개인 개발 프로젝트/Lunch Box 앱

[Lunch Box] 9. 메뉴 화면

종범2 2019. 10. 9. 14:52

상단 두 번째 탭의  MENU를 클릭하면 메뉴 화면으로 넘어간다. 메뉴에서도 특별한 기능 없이 샐랩의 메뉴만 보여준다. 주문하기 버튼이 있지만 기능 구현은 하지 않았다. 이전 화면들과 다른 점은 메뉴와 메뉴 카테고리를 나타내는 component를 만들어 이용했다는 점과 Firebase의 database에서 메뉴 정보를 가져왔다는 점이다. 이 두 가지 내용을 중점적으로 설명하고자 한다.

 

Menu.js에서는 리스트 형태로 받아오는 메뉴 정보를 보여줘야한다. 비슷한 형태에서 데이터만 달라지는데 반복적으로 보여줘야 하므로 MenuContent, MenuCategory라는 component를 만들었고 이를 menu.js에서 이용하였다.  

 

MenuContent.js

import React, { Component } from 'react';
import { withStyles } from '@material-ui/core/styles';
import myFireBase from '../../config/MyFireBase'
// Storage from firebase
const storageRef = (new myFireBase).storageRef;
const styles = () => ({
    root: {
        backgroundColor: 'white',
        height: 'auto',
        width: '100%',
        color: '#494949',
        marginTop: '1rem',
        paddingLeft: '1rem',
        paddingRight: '1rem',
        boxSizing: 'border-box',
        overflow: 'auto'
    },
    img: {
        height: 'auto',
        width: '25%',
        boxSizing: 'border-box',
        marginTop: '0.3rem',
        borderRadius: '0.1rem',
        boxShadow: '2px 2px 5px rgba(0, 0, 0, 0.1)'
    },
    content: {
        paddingLeft: "1rem",
        boxSizing: 'border-box',
        float: "right",
        height: 'auto',
        width: '75%',
    },
    contentTop: {
        height: '2rem',
        lineHeight: '2rem',
        width: '100%',
        boxSizing: 'border-box',
        borderBottom: '0.1rem dotted #cccccc',
    },
    contentTopLeft: {
        float: 'left'
    },
    contentTopRight: {
        float: 'right',
        display: 'inline-flex'
    },
    contentBottom: {
        paddingTop: '0.5rem',
        height: 'auto',
        boxSizing: 'border-box',
        width: '100%',
    },
    name: {
        fontSize: '1.2rem',
        height: '2rem',
        lineHeight: '2rem',
        float: 'left'
    },
    price: {
        fontSize: "1rem",
        float: 'right',
        height: '2rem',
        lineHeight: '2rem',
    },
    description: {
        fontSize: "1rem",
        width: '100%'
    }
})
class MenuContent extends Component {
    constructor(props) {
        super(props);
        this.state = {
            imgSrcMenu: ''
        }
    }
    componentDidMount() {
        this.getImage();
    }
    getImage() {
        storageRef.child(this.props.img + '.jpg').getDownloadURL().then((url) => {
            this.setState({ imgSrcMenu: url });
        });
    }
    render() {
        // Set classes
        const { classes } = this.props;
        // Return
        return (
            <div className={classes.root}>
                <img className={classes.img} alt="menu" src={this.state.imgSrcMenu} />
                <div className={classes.content}>
                    <div className={classes.contentTop}>
                        <div className={classes.contentTopLeft}>
                            <div className={classes.name}>{this.props.name}</div>
                        </div>
                        <div className={classes.contentTopRight}>
                            <div className={classes.price}>{this.props.price}</div>
                            <button className="lunchBox-btn-rec-line" style={{ "width": "3rem", "padding": "0", "float": "right", "marginTop": "0.25rem", "marginLeft": "1rem", "height": "1.5rem", "font-size": "0.8rem" }}>Order</button>
                        </div>
                    </div>
                    <div className={classes.contentBottom}>
                        <div className={classes.description}>{this.props.description}</div>
                    </div>
                </div>

            </div>
        );
    };
}
export default withStyles(styles)(MenuContent);

특별한 기능은 없고 상위 component에서 props로 name, price, description, img를 받아온다. 여기서 name, price, description은 받아온 내용을 그대로 보여준다. img는 이미지의 이름인데 이를 이용하여 getImage 메소드에서 Firebase Storage로부터 다운로드 URL을 불러와 state에 저장한다. Firebase Storage에서 파일을 불러오는 방법은 이전 글에서 설명하였다.

 

MenuCategory.js

import React, { Component } from 'react';
import { withStyles } from '@material-ui/core/styles';
const styles = () => ({
    root: {
        backgroundColor: '#98d865',
        height: '6em',
        width: '100%',
        paddingTop: '2rem'
    },
    text: {
        height: '4rem',
        width: '15rem',
        lineHeight: '4rem',
        textAlign: 'center',
        margin:'auto',
        color: 'white',
        fontSize: '1.5rem',
        fontWeight: 'bold',
        borderTop: '0.1rem solid white',
        borderBottom: '0.1rem solid white'
    }
})
class MenuCategory extends Component {
    render() {
        // Set classes
        const { classes } = this.props;
        // Return
        return (
            <div className={classes.root}>
                <div className={classes.text}>{this.props.text}</div>
            </div>
        );
    };
}
export default withStyles(styles)(MenuCategory);

MenuCategory component에서는 상위 component에서 props로 text를 받아오고 보여준다.

 

Firebase database

Main.js에서는 Firebase database로부터 메뉴 정보를 받아오고 두 component를 이용하여 메뉴 정보를 보여준다. 우선 Firebase dabase에 데이터를 저장해야 한다. 다음과 같이 메뉴 정보를 저장한다.

Firebase Database에 정보를 저장한 모습

개발 중이므로 인증을 설정하지 않기 위해 규칙을 다음과 같이 설정한다.

Firebase Database 규칙 설정

데이터를 입력한 후에는 menu.js에서 데이터를 불러와야 한다.

 

Menu.js

import React, { Component } from 'react';
import MenuCategory from './MenuCategory';
import MenuContent from './MenuContent';
import { withStyles } from '@material-ui/core/styles';
import myFireBase from '../../config/MyFireBase'
// Storage and database from firebase
const storageRef = (new myFireBase).storageRef;
const databaseRef = (new myFireBase).databaseRef;
const styles = () => ({
  menuTopBackground: {
    zIndex: 1,
    backgroundColor: 'rgba(0, 0, 0, 0.5)',
    width: '100%',
    height: '12rem',
    position: 'absolute'
  },
  menuTop: {
    height: '12em',
    width: '100%',
    position: 'relative',
  },
  menuTopText: {
    zIndex: 2,
    position: 'absolute',
    bottom: '3rem',
    left: '3rem',
    fontSize: "2rem",
    fontWeight: 'bold',
    color: 'white',
  },
  menuTopImg: {
    height: '12em',
    width: '100%',
    objectFit: 'cover',
    position: 'relative'
  },
  menuContentTwo: {
    display: 'inline-flex',
    marginLeft: '10%',
    marginRight: '10%',
    width: '80%',
    '@media (max-width:960px)': {
      display: 'block',
      marginLeft: '5%',
      marginRight: '5%',
      width: '90%',
    }
  },
  menuContentOne: {
    display: 'inline-flex',
    marginLeft: '10%',
    marginRight: '10%',
    width: '40%',
    '@media (max-width:960px)': {
      display: 'block',
      marginLeft: '5%',
      marginRight: '5%',
      width: '90%',
    }
  },
})
class Menu extends Component {
  constructor(props) {
    super(props);
    this.state = {
      isLogin: false,
      imgSrcMenuTop: '',
      menuBaverageData: {},
      menuSaladData: {}
    }
  }
  componentDidMount() {
    this.getMenuBaverageData();
    this.getMenuSaladData();
    this.getImage();
  }
  getImage() {
    storageRef.child('menutop.jpg').getDownloadURL().then((url) => {
      this.setState({ imgSrcMenuTop: url });
    });
  }
  // Get Data
  getMenuBaverageData() {
    databaseRef.child('menuBaverage/').once('value').then(data => {
      this.setState({ menuBaverageData: data.val() })
    })
  }
  getMenuSaladData() {
    databaseRef.child('menuSalad/').once('value').then(data => {
      this.setState({ menuSaladData: data.val() })
    })
  }
  render() {
    // Set classes
    const { classes } = this.props;
    return (
      <div>
        <div className={classes.menuTop}>
          <div className={classes.menuTopBackground}></div>
          <img alt="menuTop" className={classes.menuTopImg} src={this.state.imgSrcMenuTop} />
          <div className={classes.menuTopText}>
            Enjoy the Foods of Sallab!
          </div>
        </div>
        <MenuCategory text="SOUPS & SALADS" />
        {Object.keys((this.state.menuSaladData)).map(idx => {
          var index = parseInt(idx);
          if ((index + 1) % 2) {
            const c1 = this.state.menuSaladData[index];
            const c2 = this.state.menuSaladData[index + 1];
            if (c2 !== undefined) {
              return (
                <div className={classes.menuContentTwo}>
                  <MenuContent name={c1.name} price={c1.price} img={c1.image} description={c1.description} />
                  <MenuContent name={c2.name} price={c2.price} img={c2.image} description={c2.description} />
                </div>
              )
            } else {
              return (
                <div className={classes.menuContentOne}>
                  <MenuContent name={c1.name} price={c1.price} img={c1.image} description={c1.description} />
                </div>
              )
            }
          }
        })}
        <br></br><br />
        <MenuCategory text="BEVERAGES" />
        <div>
          {Object.keys((this.state.menuBaverageData)).map(idx => {
            var index = parseInt(idx);
            if ((index + 1) % 2) {
              const c1 = this.state.menuBaverageData[index];
              const c2 = this.state.menuBaverageData[index + 1];
              if (c2 !== undefined) {
                return (
                  <div className={classes.menuContentTwo}>
                    <MenuContent name={c1.name} price={c1.price} img={c1.image} description={c1.description} />
                    <MenuContent name={c2.name} price={c2.price} img={c2.image} description={c2.description} />
                  </div>
                )
              } else {
                return (
                  <div className={classes.menuContentOne}>
                    <MenuContent name={c1.name} price={c1.price} img={c1.image} description={c1.description} />
                  </div>
                )
              }
            }
          })}
        </div>
        <br />
      </div>
    );
  }
}

export default withStyles(styles)(Menu);

Firebase database에 저장된 파일을 사용하기 위해서는 이전에 Firebase storage 사용 방법과 유사하게 reference를 생성해서 이용해야한다. 여기서 reference란 database에 저장한 정보를 가리키는 객체로 포인터 역할을한다. Menus.js에서는 myFireBase component를 생성하고 component 내부의 database reference 객체를 databaseRef라는 변수에 저장한다.

 

다음으로 Firebase에서 데이터를 불러오기 위해 getMenuSaladData() 메소드와 getMenuBaverageData() 메소드에서 reference 객체의 once('value') 메소드를 호출한다. 이렇게 얻은 정보는 state의 menuSaladData, menuBaverageData에 저장한다. 이와 관련한 내용은 Firebase의 database 문서를 참고하였다.

 

https://firebase.google.com/docs/database/web/read-and-write

 

state로 저장한 데이터는 json 배열 객체이다. map을 이용하여 반복문 안에서 menuContent component를 생성하고 각 component에 name, price, image, description 값을 전달한다. 이때 index와 if를 이용하여 한 줄에 두개의 메뉴를 보여주고 갯수가 홀수일 경우는 하나만 보여주도록 설정했다. 또한 화면이 작을 경우에는 한줄에 하나의 메뉴만 보여주도록 style을 설정했다.