Merge pull request #398 from liordino/feature/create-receipt-printing-page
feat: create receipt printing page
This commit is contained in:
commit
d65482958a
9 changed files with 353 additions and 23 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
|
import classnames from 'classnames'
|
||||||
import React, { useState, memo } from 'react'
|
import React, { useState, memo } from 'react'
|
||||||
|
|
||||||
import { Link } from 'src/components/buttons'
|
import { Link } from 'src/components/buttons'
|
||||||
|
|
@ -30,6 +31,7 @@ const BooleanPropertiesTable = memo(
|
||||||
}
|
}
|
||||||
|
|
||||||
const innerCancel = () => {
|
const innerCancel = () => {
|
||||||
|
setRadioGroupValues(elements)
|
||||||
setEditing(false)
|
setEditing(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,8 +81,9 @@ const BooleanPropertiesTable = memo(
|
||||||
{radioGroupValues &&
|
{radioGroupValues &&
|
||||||
radioGroupValues.map((element, idx) => (
|
radioGroupValues.map((element, idx) => (
|
||||||
<TableRow key={idx} size="sm" className={classes.tableRow}>
|
<TableRow key={idx} size="sm" className={classes.tableRow}>
|
||||||
<TableCell className={classes.tableCell}>
|
<TableCell className={classes.leftTableCell}>
|
||||||
{element.display}
|
{element.display}
|
||||||
|
</TableCell>
|
||||||
{editing && (
|
{editing && (
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
options={radioButtonOptions}
|
options={radioButtonOptions}
|
||||||
|
|
@ -91,11 +94,18 @@ const BooleanPropertiesTable = memo(
|
||||||
event.target.value === 'true'
|
event.target.value === 'true'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
className={classes.radioButtons}
|
className={classnames(
|
||||||
|
classes.radioButtons,
|
||||||
|
classes.rightTableCell
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!editing && (
|
||||||
|
<BooleanCell
|
||||||
|
className={classes.rightTableCell}
|
||||||
|
value={element.value}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!editing && <BooleanCell value={element.value} />}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import baseStyles from 'src/pages/Logs.styles'
|
import baseStyles from 'src/pages/Logs.styles'
|
||||||
import { tableCellColor, zircon } from 'src/styling/variables'
|
import { backgroundColor, zircon } from 'src/styling/variables'
|
||||||
|
|
||||||
const { fillColumn } = baseStyles
|
const { fillColumn } = baseStyles
|
||||||
|
|
||||||
|
|
@ -14,20 +14,28 @@ const booleanPropertiesTableStyles = {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
'&:nth-child(even)': {
|
'&:nth-child(even)': {
|
||||||
backgroundColor: tableCellColor
|
backgroundColor: backgroundColor
|
||||||
},
|
},
|
||||||
'&:nth-child(odd)': {
|
'&:nth-child(odd)': {
|
||||||
backgroundColor: zircon
|
backgroundColor: zircon
|
||||||
},
|
},
|
||||||
|
minHeight: 32,
|
||||||
|
height: 'auto',
|
||||||
|
padding: [[8, 16, 8, 24]],
|
||||||
boxShadow: '0 0 0 0 rgba(0, 0, 0, 0)'
|
boxShadow: '0 0 0 0 rgba(0, 0, 0, 0)'
|
||||||
},
|
},
|
||||||
tableCell: {
|
leftTableCell: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'left',
|
||||||
width: '100%',
|
width: 200,
|
||||||
height: 32,
|
padding: [0]
|
||||||
padding: [[5, 14, 5, 20]]
|
},
|
||||||
|
rightTableCell: {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'right',
|
||||||
|
padding: [0]
|
||||||
},
|
},
|
||||||
transparentButton: {
|
transparentButton: {
|
||||||
'& > *': {
|
'& > *': {
|
||||||
|
|
@ -51,7 +59,7 @@ const booleanPropertiesTableStyles = {
|
||||||
radioButtons: {
|
radioButtons: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
marginRight: -15
|
margin: [-15]
|
||||||
},
|
},
|
||||||
rightLink: {
|
rightLink: {
|
||||||
marginLeft: '20px'
|
marginLeft: '20px'
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
|
import React, { useState, memo } from 'react'
|
||||||
|
|
||||||
|
import { Link } from 'src/components/buttons'
|
||||||
|
import { RadioGroup } from 'src/components/inputs'
|
||||||
|
import { H4, P } from 'src/components/typography'
|
||||||
|
import { ReactComponent as EditIconDisabled } from 'src/styling/icons/action/edit/disabled.svg'
|
||||||
|
import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg'
|
||||||
|
|
||||||
|
import { editablePropertyStyles } from './EditableProperty.styles'
|
||||||
|
|
||||||
|
const useStyles = makeStyles(editablePropertyStyles)
|
||||||
|
|
||||||
|
const EditableProperty = memo(
|
||||||
|
({ title, prefixText, disabled, options, code, save }) => {
|
||||||
|
const [editing, setEditing] = useState(false)
|
||||||
|
const [currentCode, setCurrentCode] = useState(code)
|
||||||
|
|
||||||
|
const classes = useStyles()
|
||||||
|
|
||||||
|
const innerSave = () => {
|
||||||
|
save(currentCode)
|
||||||
|
setEditing(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const innerCancel = () => setEditing(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={classes.rowWrapper}>
|
||||||
|
<H4>{title}</H4>
|
||||||
|
{editing ? (
|
||||||
|
<div className={classes.leftSpace}>
|
||||||
|
<Link
|
||||||
|
className={classes.leftSpace}
|
||||||
|
onClick={innerCancel}
|
||||||
|
color="secondary">
|
||||||
|
Cancel
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
className={classes.leftSpace}
|
||||||
|
onClick={innerSave}
|
||||||
|
color="primary">
|
||||||
|
Save
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className={classes.transparentButton}>
|
||||||
|
<button disabled={disabled} onClick={() => setEditing(true)}>
|
||||||
|
{disabled ? <EditIconDisabled /> : <EditIcon />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{editing ? (
|
||||||
|
<RadioGroup
|
||||||
|
options={options}
|
||||||
|
value={currentCode}
|
||||||
|
onChange={event => setCurrentCode(event.target.value)}
|
||||||
|
className={classes.radioButtons}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<P>
|
||||||
|
{`${prefixText} ${options
|
||||||
|
.find(it => it.code === currentCode)
|
||||||
|
.display.toLowerCase()}`}
|
||||||
|
</P>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export default EditableProperty
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
const editablePropertyStyles = {
|
||||||
|
transparentButton: {
|
||||||
|
'& > *': {
|
||||||
|
margin: 'auto 12px'
|
||||||
|
},
|
||||||
|
'& button': {
|
||||||
|
border: 'none',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
cursor: 'pointer'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rowWrapper: {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
position: 'relative',
|
||||||
|
flex: 'wrap'
|
||||||
|
},
|
||||||
|
rightAligned: {
|
||||||
|
display: 'flex',
|
||||||
|
position: 'absolute',
|
||||||
|
right: 0
|
||||||
|
},
|
||||||
|
radioButtons: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row'
|
||||||
|
},
|
||||||
|
leftSpace: {
|
||||||
|
marginLeft: '20px'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { editablePropertyStyles }
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
import EditableProperty from './EditableProperty'
|
||||||
|
|
||||||
|
export { EditableProperty }
|
||||||
|
|
@ -9,6 +9,7 @@ import logsStyles from '../Logs.styles'
|
||||||
|
|
||||||
import CoinAtmRadar from './CoinATMRadar'
|
import CoinAtmRadar from './CoinATMRadar'
|
||||||
import ContactInfo from './ContactInfo'
|
import ContactInfo from './ContactInfo'
|
||||||
|
import ReceiptPrinting from './ReceiptPrinting'
|
||||||
import TermsConditions from './TermsConditions'
|
import TermsConditions from './TermsConditions'
|
||||||
|
|
||||||
const localStyles = {
|
const localStyles = {
|
||||||
|
|
@ -52,6 +53,7 @@ const OperatorInfo = () => {
|
||||||
/>
|
/>
|
||||||
<div className={classes.contentWrapper}>
|
<div className={classes.contentWrapper}>
|
||||||
{isSelected(CONTACT_INFORMATION) && <ContactInfo />}
|
{isSelected(CONTACT_INFORMATION) && <ContactInfo />}
|
||||||
|
{isSelected(RECEIPT) && <ReceiptPrinting />}
|
||||||
{isSelected(TERMS_CONDITIONS) && <TermsConditions />}
|
{isSelected(TERMS_CONDITIONS) && <TermsConditions />}
|
||||||
{isSelected(COIN_ATM_RADAR) && <CoinAtmRadar />}
|
{isSelected(COIN_ATM_RADAR) && <CoinAtmRadar />}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,187 @@
|
||||||
|
// import { makeStyles } from '@material-ui/core/styles'
|
||||||
|
import { useQuery, useMutation } from '@apollo/react-hooks'
|
||||||
|
import { gql } from 'apollo-boost'
|
||||||
|
import React, { useState, memo } from 'react'
|
||||||
|
|
||||||
|
import { BooleanPropertiesTable } from 'src/components/booleanPropertiesTable'
|
||||||
|
import { EditableProperty } from 'src/components/editableProperty'
|
||||||
|
// import { ActionButton } from 'src/components/buttons'
|
||||||
|
// import { ReactComponent as UploadIcon } from 'src/styling/icons/button/upload/zodiac.svg'
|
||||||
|
// import { ReactComponent as UploadIconInverse } from 'src/styling/icons/button/upload/white.svg'
|
||||||
|
// import { TextInput } from 'src/components/inputs'
|
||||||
|
|
||||||
|
// import { mainStyles } from './ReceiptPrinting.styles'
|
||||||
|
|
||||||
|
// const useStyles = makeStyles(mainStyles)
|
||||||
|
|
||||||
|
const initialValues = {
|
||||||
|
active: 'off',
|
||||||
|
// logo: false,
|
||||||
|
operatorWebsite: false,
|
||||||
|
operatorEmail: false,
|
||||||
|
operatorPhone: false,
|
||||||
|
companyRegistration: false,
|
||||||
|
machineLocation: false,
|
||||||
|
customerNameOrPhoneNumber: false,
|
||||||
|
// commission: false,
|
||||||
|
exchangeRate: false,
|
||||||
|
addressQRCode: false
|
||||||
|
// customText: false,
|
||||||
|
// customTextContent: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const GET_CONFIG = gql`
|
||||||
|
{
|
||||||
|
config
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const SAVE_CONFIG = gql`
|
||||||
|
mutation Save($config: JSONObject) {
|
||||||
|
saveConfig(config: $config)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const receiptPrintingOptions = [
|
||||||
|
{
|
||||||
|
code: 'off',
|
||||||
|
display: 'Off'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'optional',
|
||||||
|
display: 'Optional (ask user)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'on',
|
||||||
|
display: 'On'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const ReceiptPrinting = memo(() => {
|
||||||
|
const [receiptPrintingConfig, setReceiptPrintingConfig] = useState(null)
|
||||||
|
|
||||||
|
// const classes = useStyles()
|
||||||
|
|
||||||
|
// TODO: treat errors on useMutation and useQuery
|
||||||
|
const [saveConfig] = useMutation(SAVE_CONFIG, {
|
||||||
|
onCompleted: configResponse =>
|
||||||
|
setReceiptPrintingConfig(configResponse.saveConfig.receiptPrinting)
|
||||||
|
})
|
||||||
|
useQuery(GET_CONFIG, {
|
||||||
|
onCompleted: configResponse => {
|
||||||
|
setReceiptPrintingConfig(
|
||||||
|
configResponse?.config?.receiptPrinting ?? initialValues
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const save = it =>
|
||||||
|
saveConfig({ variables: { config: { receiptPrinting: it } } })
|
||||||
|
|
||||||
|
if (!receiptPrintingConfig) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<EditableProperty
|
||||||
|
title={'Receipt options'}
|
||||||
|
prefixText={'Receipt printing'}
|
||||||
|
disabled={false}
|
||||||
|
options={receiptPrintingOptions}
|
||||||
|
code={receiptPrintingConfig.active}
|
||||||
|
save={it =>
|
||||||
|
saveConfig({
|
||||||
|
variables: { config: { receiptPrinting: { active: it } } }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<BooleanPropertiesTable
|
||||||
|
title={'Visible on the receipt (optionals)'}
|
||||||
|
disabled={receiptPrintingConfig.active === 'off'}
|
||||||
|
data={receiptPrintingConfig}
|
||||||
|
elements={[
|
||||||
|
// {
|
||||||
|
// name: 'logo',
|
||||||
|
// display: (
|
||||||
|
// <>
|
||||||
|
// {'Logo'}
|
||||||
|
// <ActionButton
|
||||||
|
// className={classes.actionButton}
|
||||||
|
// Icon={UploadIcon}
|
||||||
|
// InverseIcon={UploadIconInverse}
|
||||||
|
// color={'primary'}
|
||||||
|
// onClick={() => {
|
||||||
|
// // TODO: make the replace logo feature
|
||||||
|
// }}>
|
||||||
|
// Replace logo
|
||||||
|
// </ActionButton>
|
||||||
|
// </>
|
||||||
|
// ),
|
||||||
|
// value: receiptPrintingConfig.logo
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
name: 'operatorWebsite',
|
||||||
|
display: 'Operator website',
|
||||||
|
value: receiptPrintingConfig.operatorWebsite
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'operatorEmail',
|
||||||
|
display: 'Operator email',
|
||||||
|
value: receiptPrintingConfig.operatorEmail
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'operatorPhone',
|
||||||
|
display: 'Operator phone',
|
||||||
|
value: receiptPrintingConfig.operatorPhone
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'companyRegistration',
|
||||||
|
display: 'Company registration',
|
||||||
|
value: receiptPrintingConfig.companyRegistration
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'machineLocation',
|
||||||
|
display: 'Machine location',
|
||||||
|
value: receiptPrintingConfig.machineLocation
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'customerNameOrPhoneNumber',
|
||||||
|
display: 'Customer name or phone number (if known)',
|
||||||
|
value: receiptPrintingConfig.customerNameOrPhoneNumber
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// name: 'commission',
|
||||||
|
// display: 'Commission',
|
||||||
|
// value: receiptPrintingConfig.commission
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
name: 'exchangeRate',
|
||||||
|
display: 'Exchange rate',
|
||||||
|
value: receiptPrintingConfig.exchangeRate
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'addressQRCode',
|
||||||
|
display: 'Address QR code',
|
||||||
|
value: receiptPrintingConfig.addressQRCode
|
||||||
|
}
|
||||||
|
// {
|
||||||
|
// name: 'customText',
|
||||||
|
// display: 'Custom text',
|
||||||
|
// value: receiptPrintingConfig.customText
|
||||||
|
// }
|
||||||
|
]}
|
||||||
|
save={save}
|
||||||
|
/>
|
||||||
|
{/* TODO: textInput should appear only when table is in edit mode, and have it's value saved along with the table values */}
|
||||||
|
{/* <TextInput
|
||||||
|
className={classes.textInput}
|
||||||
|
label={'Custom text content'}
|
||||||
|
multiline
|
||||||
|
rows="4"
|
||||||
|
defaultValue={receiptPrintingConfig.customTextContent}
|
||||||
|
/> */}
|
||||||
|
{/* TODO: add receipt preview on the right side of the page */}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default ReceiptPrinting
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
const mainStyles = {
|
||||||
|
textInput: {
|
||||||
|
margin: [[28, 20]],
|
||||||
|
width: 304
|
||||||
|
},
|
||||||
|
actionButton: {
|
||||||
|
margin: [[0, 24]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { mainStyles }
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
import ReceiptPrinting from './ReceiptPrinting'
|
||||||
|
|
||||||
|
export default ReceiptPrinting
|
||||||
Loading…
Add table
Add a link
Reference in a new issue