Skip to content

Commit

Permalink
Merge pull request #239 from qccoders/manual-scanning
Browse files Browse the repository at this point in the history
Allow manual barcode entry
  • Loading branch information
wburklund authored Oct 8, 2018
2 parents 349c38f + bd81167 commit ea31ca9
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 34 deletions.
132 changes: 132 additions & 0 deletions web/src/scans/ManualScanDialog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
Copyright (c) QC Coders. All rights reserved. Licensed under the GPLv3 license. See LICENSE file
in the project root for full license information.
*/

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import {
Dialog,
DialogTitle,
DialogActions,
Button,
DialogContent,
TextField,
} from '@material-ui/core';

const styles = {
dialog: {
width: 320,
marginLeft: 'auto',
marginRight: 'auto',
marginTop: 50,
height: 'fit-content',
},
};

const initialState = {
cardNumber: undefined,
validation: {
cardNumber: undefined,
},
};

class ManualScanDialog extends Component {
state = initialState;

componentWillReceiveProps = (nextProps) => {
if (nextProps.open && !this.props.open) {
this.setState({
...initialState
});
}
}

handleCancelClick = () => {
this.props.onClose(undefined);
}

handleEnterClick = () => {
this.validate().then(result => {
if (result.isValid) {
this.props.onClose(this.state.cardNumber);
}
});
}

handleChange = (event) => {
this.setState({
cardNumber: event.target.value,
validation: { cardNumber: undefined },
});
}

validate = () => {
let result = { ...initialState.validation };

if (this.state.cardNumber === undefined) {
result.cardNumber = 'A Card Number is required.';
}

if (isNaN(this.state.cardNumber)) {
result.cardNumber = 'The Card Number must contain only numbers.';
}

return new Promise(resolve => {
this.setState({ validation: result }, () => {
result.isValid = JSON.stringify(result) === JSON.stringify(initialState.validation);
resolve(result);
});
})
}

render() {
let { classes, open } = this.props;
let { validation } = this.state;

return (
<Dialog
open={open}
onClose={this.handleCancelClick}
PaperProps={{ className: classes.dialog }}
scroll={'body'}
>
<DialogTitle>Enter Card Number</DialogTitle>
<DialogContent>
<TextField
style={{marginTop: 0}}
id="cardNumber"
label="Card Number"
type="numeric"
error={validation.cardNumber !== undefined}
helperText={validation.cardNumber}
fullWidth
onChange={(event) => this.handleChange(event)}
/>
</DialogContent>
<DialogActions>
<Button
onClick={this.handleCancelClick}
color="primary"
>
Cancel
</Button>
<Button
onClick={this.handleEnterClick}
color="primary"
>
Enter
</Button>
</DialogActions>
</Dialog>
);
}
}

ManualScanDialog.propTypes = {
classes: PropTypes.object.isRequired,
open: PropTypes.bool.isRequired,
};

export default withStyles(styles)(ManualScanDialog);
108 changes: 76 additions & 32 deletions web/src/scans/Scanner.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import ScannerMenu from './ScannerMenu';

import ScannerHistoryDialog from './ScannerHistoryDialog';
import { getScanResult } from './scannerUtil';
import ManualScanDialog from './ManualScanDialog';

const historyLimit = 5;

Expand All @@ -45,6 +46,17 @@ const styles = {
marginRight: 'auto',
marginTop: 68,
},
scanSpinner: {
position: 'fixed',
top: 0,
bottom: 0,
left: 0,
right: 0,
marginTop: 'auto',
marginBottom: 'auto',
marginLeft: 'auto',
marginRight: 'auto',
},
displayBox: {
display: 'flex',
justifyContent: 'center',
Expand All @@ -65,6 +77,10 @@ const initialState = {
isExecuting: false,
isErrored: false,
},
scanApi: {
isExecuting: false,
isErrored: false,
},
scanner: {
event: undefined,
service: undefined,
Expand All @@ -79,6 +95,9 @@ const initialState = {
historyDialog: {
open: false,
},
scanDialog: {
open: false,
},
}

class Scanner extends Component {
Expand All @@ -91,14 +110,26 @@ class Scanner extends Component {
}

handleBarcodeScanned = (barcode) => {
if (barcode === undefined) return;

let { event, service } = this.state.scanner;
let scan = { eventId: event && event.id, serviceId: service && service.id, cardNumber: barcode };

api.put('/v1/scans', scan)
.then(response => {
this.handleScanResponse(barcode, response);
}, error => {
this.handleScanResponse(barcode, error.response);
this.setState({
scan: initialState.scan,
scanDialog: { open: false },
scanApi: { ...this.state.scanApi, isExecuting: true }
}, () => {
api.put('/v1/scans', scan)
.then(response => {
this.setState({ scanApi: { isExecuting: false, isErrored: false }}, () => {
this.handleScanResponse(barcode, response);
});
}, error => {
this.setState({ scanApi: { isExecuting: false, isErrored: true }}, () => {
this.handleScanResponse(barcode, error.response);
});
});
});
}

Expand All @@ -107,10 +138,18 @@ class Scanner extends Component {
initiateMobileScan();
}
else {
// TODO: manual input of barcode
this.setState({ scanDialog: { open: true }});
}
}

handleScanDialogClose = (result) => {
this.setState({ scanDialog: { open: false }}, () => {
if (result !== undefined) {
this.handleBarcodeScanned(result);
}
});
}

handleScanResponse = (cardNumber, response) => {
let scan = { cardNumber: cardNumber, status: response.status, response: response.data };

Expand Down Expand Up @@ -225,7 +264,7 @@ class Scanner extends Component {

render() {
let classes = this.props.classes;
let { loadApi, refreshApi, scanner, scan, events, services, history, historyDialog } = this.state;
let { loadApi, refreshApi, scanApi, scanner, scan, events, services, history, historyDialog, scanDialog } = this.state;

let title = this.getTitle(scanner);
let display = this.getScanDisplay(scan);
Expand Down Expand Up @@ -259,31 +298,32 @@ class Scanner extends Component {
viewHistory={() => this.setState({ historyDialog: { open: true }})}
/>
</div>
{refreshApi.isExecuting ?
<CircularProgress size={30} color={'secondary'} className={classes.refreshSpinner}/> :
<div>
{!eventSelected &&
<EventList
events={events}
icon={<Today/>}
onItemClick={this.handleEventItemClick}
/>
}
{!serviceSelected && eventSelected &&
<ServiceList
services={services}
icon={<Shop/>}
onItemClick={this.handleServiceItemClick}
/>
}
{serviceSelected && eventSelected &&
<div className={classes.displayBox}>
{!scan.status ? <Button disabled>Ready to Scan</Button> :
display
}
</div>
}
</div>
{scanApi.isExecuting ? <CircularProgress thickness={7.2} size={72} color={'secondary'} className={classes.scanSpinner}/> :
refreshApi.isExecuting ?
<CircularProgress size={30} color={'secondary'} className={classes.refreshSpinner}/> :
<div>
{!eventSelected &&
<EventList
events={events}
icon={<Today/>}
onItemClick={this.handleEventItemClick}
/>
}
{!serviceSelected && eventSelected &&
<ServiceList
services={services}
icon={<Shop/>}
onItemClick={this.handleServiceItemClick}
/>
}
{serviceSelected && eventSelected &&
<div className={classes.displayBox}>
{!scan.status ? <Button disabled>Ready to Scan</Button> :
display
}
</div>
}
</div>
}
</CardContent>
</Card>
Expand All @@ -295,6 +335,10 @@ class Scanner extends Component {
>
<SpeakerPhone/>
</Button>}
<ManualScanDialog
open={scanDialog.open}
onClose={this.handleScanDialogClose}
/>
<ScannerHistoryDialog
open={historyDialog.open}
history={history}
Expand Down
17 changes: 15 additions & 2 deletions web/src/shared/ConfirmDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import DialogActions from '@material-ui/core/DialogActions';
import Dialog from '@material-ui/core/Dialog';
import CircularProgress from '@material-ui/core/CircularProgress';

import { withStyles } from '@material-ui/core/styles';

const initialState = {
api: {
isExecuting: false,
Expand All @@ -22,7 +24,14 @@ const initialState = {
const styles = {
spinner: {
position: 'fixed',
}
},
dialog: {
width: 320,
marginLeft: 'auto',
marginRight: 'auto',
marginTop: 50,
height: 'fit-content',
},
}

class ConfirmDialog extends Component {
Expand Down Expand Up @@ -56,12 +65,16 @@ class ConfirmDialog extends Component {

render() {
let additionalProps = { ...this.props };

delete additionalProps.classes;
delete additionalProps.onConfirm;
delete additionalProps.suppressCloseOnConfirm;

return (
<Dialog
PaperProps={{ className: this.props.classes.dialog }}
{...additionalProps}
scroll={'body'}
>
<DialogTitle>{this.props.title}</DialogTitle>
<DialogContent>
Expand Down Expand Up @@ -96,4 +109,4 @@ ConfirmDialog.propTypes = {
suppressCloseOnConfirm: PropTypes.bool,
};

export default ConfirmDialog;
export default withStyles(styles)(ConfirmDialog);

0 comments on commit ea31ca9

Please sign in to comment.