project demo: https://chuanshuoge1-transaction-paper.herokuapp.com/
left: react front end. right: mongoDB cloud back end
click add button, dialog opens, name is required. name cateogry, amount are submited
new transcation added to mondoDB cloud
click edit button, change category to travel, submit
category updated to travel
delete dialog
record deleted
--client
--public
--index.html
--src
--index.js
--App.js
--App.css
--forms
--create.js
--delete.js
--display.js
--icons.js
--update.js
--package.json
----------------------------------------------------------------
//add dependencies
//apollo provide accsess to localhost server, graphql allows queries and handle data
npm install apollo-boost react-apollo graphql-tag graphql --save
//add material-ui for front end styling
npm install @material-ui/core
npm install @material-ui/icons
--------------------------------------------------------------------
//package.json
{
"name": "client",
"version": "0.1.0",
"private": true,
"dependencies": {
"@material-ui/core": "^1.3.0",
"@material-ui/icons": "^1.1.0",
"react": "^16.4.1",
"react-dom": "^16.4.1",
"react-scripts": "1.1.4"
"apollo-boost": "^0.1.10",
"graphql": "^0.13.2",
"graphql-tag": "^2.9.2",
"react-apollo": "^2.1.8",
"react-text-mask": "^5.4.2"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}
---------------------------------------------------
//add css style for material-ui
//client/public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500">
<title>React App</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
</body>
</html>
-------------------------------------------------------
//client/src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import ApolloClient from "apollo-boost";
import { ApolloProvider } from "react-apollo";
const client = new ApolloClient({
uri: "http://localhost:4000"
});
ReactDOM.render(
<ApolloProvider client={client}><App /></ApolloProvider>,
document.getElementById('root'));
registerServiceWorker();
------------------------------------------------------
//App.js
import React, { Component } from 'react';
import Display from './forms/display';
import Create from './forms/create';
import Update from './forms/update';
import Delete from './forms/delete';
import { categoryText } from './forms/icons';
import './App.css';
import gql from "graphql-tag";
import { graphql, compose } from 'react-apollo';
import Paper from '@material-ui/core/Paper';
const profileQuery = gql`{
profiles{
id
name
amount
category
date
}
}
`;
const createProfile = gql`
mutation($name: String!, $category: Int, $amount: Float){
createProfile(
name: $name,
category: $category,
amount: $amount){
id,
name,
amount,
date,
category
}
}
`;
const deleteProfile = gql`
mutation($id: ID!){
removeProfile(id: $id)
}
`;
const updateProfile = gql`
mutation($id: ID!, $name: String!, $category: Int, $amount: Float){
updateProfile(
id: $id,
name: $name,
category: $category,
amount: $amount,
)
}
`;
class App extends Component {
constructor(props) {
super(props);
this.state = {
createFormOpen: false,
updateFormOpen: false,
deleteFormOpen: false,
updateId: "",
updateName: "",
updateCategory: 0,
updateAmount: 0,
deleteId: "",
};
}
handleClick_addButton() {
this.setState({ createFormOpen:true })
}
handleClick_updateButton(id) {
const profile = this.props.data.profiles.filter(x => x.id === id)[0]
this.setState({
updateFormOpen: true,
updateId: id,
updateName: profile.name,
updateCategory: profile.category,
updateAmount: profile.amount,
})
}
handleClick_deleteButton(id) {
this.setState({
deleteFormOpen: true,
deleteId: id,
})
}
handleNameChange(e) {
this.setState({ updateName: e.target.value, });
}
handleCategoryChange( e) {
this.setState({ updateCategory: parseInt( e.target.value) });
}
handleAmountChange(e) {
this.setState({ updateAmount: parseFloat(e.target.value) });
}
handleClick_createCancel() {
this.setState({ createFormOpen: false })
}
handleClick_updateCancel() {
this.setState({ updateFormOpen: false })
}
handleClick_deleteCancel() {
this.setState({deleteFormOpen: false})
}
handleClick_createSubmit(a,b,c) {
this.setState({ createFormOpen: false, });
this.handle_createProfile(a, b, c);
alert("new profile created. Name " + a + ". Category: " + categoryText[b] +
". Amount: "+c);
}
handleClick_updateSubmit() {
this.setState({ updateFormOpen: false, });
this.handle_updateProfile(
this.state.updateId,
this.state.updateName,
this.state.updateCategory,
this.state.updateAmount );
alert("profile updated. Name " + this.state.updateName +
". Category: " + categoryText[this.state.updateCategory] +
". Amount: " + this.state.updateAmount);
}
handleClick_deleteSubmit() {
this.setState({ deleteFormOpen: false });
this.handle_deleteProfile(this.state.deleteId);
}
async handle_createProfile(a,b,c) {
await this.props.createP({
variables: {
name: a,
category: b,
amount: c,
},
refetchQueries: [{
query: profileQuery,
}],
})
}
async handle_deleteProfile(id) {
await this.props.deleteP({
variables: {
id: id,
},
refetchQueries: [{
query: profileQuery,
}],
});
const deletedProfile = this.props.data.profiles.filter(x => x.id === id);
alert("profile deleted. Name " + deletedProfile[0].name +
". Category: " + categoryText[deletedProfile[0].category] +
". Amount: " + deletedProfile[0].amount);
}
async handle_updateProfile(id, a, b, c) {
await this.props.updateP({
variables: {
id: id,
name: a,
category: b,
amount: c,
},
refetchQueries: [{
query: profileQuery,
}],
})
}
render() {
const profiles = this.props.data.loading ? <div></div> : <div>
{/* ------------display list------------------- */}
<Display
profiles={this.props.data.profiles}
callBack_add={() => this.handleClick_addButton()}
callBack_delete={(id) => this.handleClick_deleteButton(id)}
callBack_update={(id) => this.handleClick_updateButton(id)}/>
{/* ------------create dialog------------------- */}
<Create open={this.state.createFormOpen}
callBack_cancel={() => this.handleClick_createCancel()}
callBack_submit={(name, category, amount) => this.handleClick_createSubmit(name, category, amount)} />
{/* ------------delete dialog------------------- */}
{
this.state.deleteId !== "" ?
<Delete open={this.state.deleteFormOpen}
callBack_cancel={() => this.handleClick_deleteCancel()}
callBack_submit={() => this.handleClick_deleteSubmit()} />
: null
}
{/* ------------update dialog------------------- */}
{
this.state.updateId!==""?
<Update open={this.state.updateFormOpen}
name={this.state.updateName}
category={this.state.updateCategory}
amount={this.state.updateAmount}
nameChange_callBack={( e) => this.handleNameChange( e)}
categoryChange_callBack={( e) => this.handleCategoryChange( e)}
amountChange_callBack={(e)=>this.handleAmountChange(e)}
callBack_cancel={() => this.handleClick_updateCancel()}
callBack_submit={() =>this.handleClick_updateSubmit()} />
: null
}
</div>
return (
<div style={{ display: "flex" }}>
<div className="transaction-list">
<Paper elevation={1}>
{profiles}
</Paper>
</div>
</div>
);
}
}
export default compose(
graphql(profileQuery),
graphql(createProfile, { name: 'createP' }),
graphql(deleteProfile, { name: 'deleteP' }),
graphql(updateProfile, { name: 'updateP' }),
)(App);
------------------------------------------------------------------------------
//App.css
.transaction-list{
margin: auto;
width: 400px;
}
.spending-money{
color:red;
}
.saving-money{
color:green;
}
.transaction-person {
color:blue;
font-weight: bold;
}
----------------------------------------------------------------
//src/forms/icons.js
import React from 'react';
import WorkIcon from '@material-ui/icons/Work';
import FlightIcon from '@material-ui/icons/Flight';
import BluronIcon from '@material-ui/icons/BlurOn';
import SchoolIcon from '@material-ui/icons/School';
import HomeIcon from '@material-ui/icons/Home';
import LocalGroceryStoreIcon from '@material-ui/icons/LocalGroceryStore';
import ChildCareIcon from '@material-ui/icons/ChildCare';
import LocalHospitalIcon from '@material-ui/icons/LocalHospital';
import BeachAccessIcon from '@material-ui/icons/BeachAccess';
import AddIcon from '@material-ui/icons/Add';
import EditIcon from '@material-ui/icons/Edit';
import DeleteIcon from '@material-ui/icons/Delete';
export const categoryIcons = [
<BluronIcon />,
<WorkIcon/>,
<FlightIcon />,
<SchoolIcon />,
<HomeIcon />,
<ChildCareIcon/>,
<LocalGroceryStoreIcon />,
<LocalHospitalIcon />,
<BeachAccessIcon />,
];
export const categoryText = [
"Miscellaneous",
"Work",
"Travel",
"Education",
"Housing",
"ChildCare",
"Grocery",
"HealthCare",
"Recreation",
]
export const buttonIcon = [
<AddIcon />,
<EditIcon />,
<DeleteIcon/>,
]
-------------------------------------------------
//forms/display.js
import React, { Component } from 'react';
import '../App.css';
import { categoryIcons, categoryText, buttonIcon } from './icons';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import Divider from '@material-ui/core/Divider';
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
import blue from '@material-ui/core/colors/blue';
import orange from '@material-ui/core/colors/orange';
import pink from '@material-ui/core/colors/pink';
import green from '@material-ui/core/colors/green';
import Avatar from '@material-ui/core/Avatar';
import Button from '@material-ui/core/Button';
import ListIcon from '@material-ui/icons/List';
import { withStyles } from '@material-ui/core/styles';
import classNames from 'classnames';
const styles = theme =>( {
avatar: {
margin: theme.spacing.unit,
},
button: {
margin: theme.spacing.unit,
},
blue: {
color: blue[500],
backgroundColor: blue[100],
},
orange: {
color: orange[500],
backgroundColor: orange[100],
},
pink: {
color: pink[500],
backgroundColor: pink[100],
},
green: {
color: green[500],
backgroundColor: green[100],
}
});
class DisplayClass extends Component {
constructor(props) {
super(props);
this.state = {
};
}
handleClick_add() {
this.props.callBack_add();
}
handleClick_delete(id) {
this.props.callBack_delete(id);
}
handleClick_update(id) {
this.props.callBack_update(id);
}
render() {
const { classes } = this.props;
return (
<List>
<ListItem key={-1}>
<Avatar className={classNames(classes.avatar, classes.blue)}>
<ListIcon />
</Avatar>
<ListItemText primary={<h3>Transactions</h3>} />
<ListItemSecondaryAction>
<Button variant="fab" mini className={classNames(classes.button, classes.orange)}
onClick={() => this.handleClick_add()}>
{buttonIcon[0]}
</Button>
</ListItemSecondaryAction>
</ListItem>
<Divider />
{
this.props.profiles.map((item, index) => {
const transactionClass = item.amount < 0 ? "spending-money" : "saving-money";
const primaryText = <span>
{categoryText[item.category]}
{" $ "}
<span className={transactionClass}>{item.amount.toString()}</span>
</span>;
const secondaryText = <span>
<span className="transaction-person">{item.name}</span>
{" "}
{item.date}
</span>;
return (
<div key={index}>
<ListItem >
<Avatar className={classNames(classes.avatar, classes.blue)}>
{categoryIcons[item.category]}
</Avatar>
<ListItemText primary={primaryText} secondary={secondaryText} />
<ListItemSecondaryAction>
<Button variant="fab" mini className={classNames(classes.button, classes.green)}
onClick={() => this.handleClick_update(item.id)}>
{buttonIcon[1]}
</Button>
<Button variant="fab" mini className={classNames(classes.button, classes.pink)}
onClick={() => this.handleClick_delete(item.id)} >
{buttonIcon[2]}
</Button>
</ListItemSecondaryAction>
</ListItem>
<Divider />
</div>);
}
)
}
</List>
);
}
}
export default withStyles(styles)(DisplayClass);
----------------------------------------------------
//forms/create.js
import React, { Component } from 'react';
import '../App.css';
import { categoryIcons, categoryText } from './icons';
import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';
import MenuItem from '@material-ui/core/MenuItem';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';
import Input from '@material-ui/core/Input';
import InputLabel from '@material-ui/core/InputLabel';
import InputAdornment from '@material-ui/core/InputAdornment';
import FormHelperText from '@material-ui/core/FormHelperText';
import FormControl from '@material-ui/core/FormControl';
import MaskedInput from 'react-text-mask';
import Visibility from '@material-ui/icons/Visibility';
import VisibilityOff from '@material-ui/icons/VisibilityOff';
import IconButton from '@material-ui/core/IconButton';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import Avatar from '@material-ui/core/Avatar';
import blue from '@material-ui/core/colors/blue';
const styles = theme => ({
container: {
display: 'flex',
flexWrap: 'wrap',
},
textField: {
marginLeft: theme.spacing.unit,
marginRight: theme.spacing.unit,
width: 200,
},
menu: {
width: 200,
},
smallAvatar: {
marginRight: theme.spacing.unit,
marginTop: -5,
width: 30,
height: 30,
color: blue[500],
backgroundColor: blue[100],
},
row: {
display: 'flex',
justifyContent: 'center',
},
margin: {
margin: theme.spacing.unit,
},
marginPassword: {
margin: theme.spacing.unit,
flexBasis: 200,
}
});
function TextMaskCustom(props) {
const { inputRef, ...other } = props;
return (
<MaskedInput
{...other}
ref={inputRef}
mask={['(', /[1-9]/, /\d/, /\d/, ')', ' ', /\d/, /\d/, /\d/, '-', /\d/, /\d/, /\d/, /\d/]}
placeholderChar={'\u2000'}
showMask
/>
);
}
class CreateClass extends Component {
constructor(props) {
super(props);
this.state = {
category: 0,
name: "",
nameError: false,
amount: 0,
textmask: '(1 ) - ',
password: '',
showPassword: false,
};
}
handleNameChange( e) {
this.setState({ name: e.target.value,})
}
handleCategoryChange(e) {
this.setState({ category: parseInt( e.target.value) });
}
handleAmountChange(e) {
this.setState({ amount: parseFloat(e.target.value) });
}
handleCancel() {
this.props.callBack_cancel();
}
handleSubmit() {
this.setState({ nameError: false, });
if (this.state.name === "") {
this.setState({ nameError: true, });
}
else {
this.props.callBack_submit(
this.state.name,
this.state.category,
this.state.amount,
);
}
}
handleMouseDownPassword = event => {
event.preventDefault();
};
handleClickShowPassword = () => {
this.setState(state => ({ showPassword: !state.showPassword }));
};
render() {
const { classes } = this.props;
const selectedCategory = categoryText[this.state.category];
return (
<Dialog
open={this.props.open}
onClose={this.handleClose}
aria-labelledby="form-dialog-title"
>
<DialogTitle id="form-dialog-title">Add transcation</DialogTitle>
<DialogContent>
<DialogContentText>
Name is required
</DialogContentText>
<form className={classes.container} noValidate autoComplete="off">
<TextField
required
error={this.state.nameError}
id="name"
label="Name"
className={classes.textField}
margin="normal"
onChange={(e) => this.handleNameChange(e)}
/>
<TextField
id="select-category"
select
label="Category"
className={classes.textField}
value={this.state.category}
onChange={(e) => this.handleCategoryChange(e)}
SelectProps={{
MenuProps: {
className: classes.menu,
},
}}
margin="normal"
>
{categoryText.map((item, index) => (
<MenuItem key={index} value={index}>
<div className={classes.row}>
<Avatar className={classes.smallAvatar}>
{categoryIcons[index]}
</Avatar>
{item}
</div>
</MenuItem>
))}
</TextField>
<FormControl className={classes.margin}>
<InputLabel htmlFor="amount">Amount</InputLabel>
<Input
id="amount"
defaultValue={this.state.amount}
onChange={(e)=>this.handleAmountChange(e)}
startAdornment={<InputAdornment position="start">$</InputAdornment>}
/>
</FormControl>
<FormControl className={classes.margin}>
<InputLabel htmlFor="formatted-text-mask-input">Phone#</InputLabel>
<Input
value={this.state.textmask}
id="formatted-text-mask-input"
inputComponent={TextMaskCustom}
/>
</FormControl>
<FormControl className={classes.marginPassword}>
<InputLabel htmlFor="adornment-password">Password</InputLabel>
<Input
id="adornment-password"
type={this.state.showPassword ? 'text' : 'password'}
defaultValue={this.state.password}
endAdornment={
<InputAdornment position="end">
<IconButton
aria-label="Toggle password visibility"
onClick={this.handleClickShowPassword}
onMouseDown={this.handleMouseDownPassword}
>
{this.state.showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
}
/>
</FormControl>
</form>
</DialogContent>
<DialogActions>
<Button onClick={() => this.handleCancel()} color="primary">
Cancel
</Button>
<Button onClick={() => this.handleSubmit()} color="primary">
Submit
</Button>
</DialogActions>
</Dialog>
);
}
}
export default withStyles(styles)(CreateClass);
---------------------------------------------------------------
//forms/update.js
import React, { Component } from 'react';
import '../App.css';
import { categoryIcons, categoryText } from './icons';
import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';
import MenuItem from '@material-ui/core/MenuItem';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';
import Input from '@material-ui/core/Input';
import InputLabel from '@material-ui/core/InputLabel';
import InputAdornment from '@material-ui/core/InputAdornment';
import FormHelperText from '@material-ui/core/FormHelperText';
import FormControl from '@material-ui/core/FormControl';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import Avatar from '@material-ui/core/Avatar';
import blue from '@material-ui/core/colors/blue';
const styles = theme => ({
container: {
display: 'flex',
flexWrap: 'wrap',
},
textField: {
marginLeft: theme.spacing.unit,
marginRight: theme.spacing.unit,
width: 200,
},
menu: {
width: 200,
},
smallAvatar: {
marginRight: theme.spacing.unit,
marginTop: -5,
width: 30,
height: 30,
color: blue[500],
backgroundColor: blue[100],
},
row: {
display: 'flex',
justifyContent: 'center',
},
margin: {
margin: theme.spacing.unit,
},
});
class UpdateClass extends Component {
constructor(props) {
super(props);
this.state = {
nameError: false,
category: props.category,
};
}
handleCancel() {
this.props.callBack_cancel();
}
handleSubmit() {
this.setState({ nameError: false, });
if (this.state.name === "") {
this.setState({ nameError: true, });
}
else {
this.props.callBack_submit();
}
}
render() {
const { classes } = this.props;
return (
<Dialog
open={this.props.open}
onClose={this.handleClose}
aria-labelledby="form-dialog-title"
>
<DialogTitle id="form-dialog-title">Update transcation</DialogTitle>
<DialogContent>
<DialogContentText>
Name is required
</DialogContentText>
<form className={classes.container} noValidate autoComplete="off">
<TextField
required
error={this.state.nameError}
id="name"
label="Name"
className={classes.textField}
margin="normal"
onChange={(e) => this.props.nameChange_callBack( e)}
defaultValue={this.props.name}
/>
<TextField
id="select-category"
select
label="Category"
className={classes.textField}
value={this.props.category}
onChange={(e) => this.props.categoryChange_callBack( e)}
SelectProps={{
MenuProps: {
className: classes.menu,
},
}}
margin="normal"
>
{categoryText.map((item, index) => (
<MenuItem key={index} value={index}>
<div className={classes.row}>
<Avatar className={classes.smallAvatar}>
{categoryIcons[index]}
</Avatar>
{item}
</div>
</MenuItem>
))}
</TextField>
<FormControl className={classes.margin}>
<InputLabel htmlFor="amount">Amount</InputLabel>
<Input
id="amount"
onChange={(e) => this.props.amountChange_callBack( e)}
startAdornment={<InputAdornment position="start">$</InputAdornment>}
defaultValue={this.props.amount}
/>
</FormControl>
</form>
</DialogContent>
<DialogActions>
<Button onClick={() => this.handleCancel()} color="primary">
Cancel
</Button>
<Button onClick={() => this.handleSubmit()} color="primary">
Submit
</Button>
</DialogActions>
</Dialog>
);
}
}
export default withStyles(styles)(UpdateClass);
-------------------------------------------------
//forms/delete.js
import React, { Component } from 'react';
import '../App.css';
import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
const styles = theme => ({
container: {
display: 'flex',
flexWrap: 'wrap',
},
});
class DeleteClass extends Component {
constructor(props) {
super(props);
this.state = {
};
}
handleCancel() {
this.props.callBack_cancel();
}
handleSubmit() {
this.props.callBack_submit();
}
render() {
const { classes } = this.props;
return (
<Dialog
open={this.props.open}
onClose={this.handleClose}
aria-labelledby="form-dialog-title"
>
<DialogTitle id="form-dialog-title">Delete transcation</DialogTitle>
<DialogContent>
<DialogContentText>
Confirm delete?
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => this.handleCancel()} color="primary">
Cancel
</Button>
<Button onClick={() => this.handleSubmit()} color="primary">
Confirm
</Button>
</DialogActions>
</Dialog>
);
}
}
export default withStyles(styles)(DeleteClass);
------------------------------------
reference:
http://chuanshuoge2.blogspot.com/2018/07/react-material-ui.html
http://chuanshuoge2.blogspot.com/2018/06/react-mongodb.html
reference:
http://chuanshuoge2.blogspot.com/2018/07/react-material-ui.html
http://chuanshuoge2.blogspot.com/2018/06/react-mongodb.html
No comments:
Post a Comment