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

[Lunch Box] 11. 공지 상세 화면

종범2 2019. 10. 13. 23:08

공지 화면에서 게시판 형태의 데이터 중 특정 행을 클릭하면 공지 상세 화면으로 넘어간다. 공지 상세 화면에서는 공지 화면으로부터 클릭한 행의 상세 정보를 받아 보여주며 댓글 기능을 제공한다. 스타일은 이전 화면과 동일하게 withStyles을 이용하였고 댓글 정보와 이미지를 Firebase에서 받아 오는 방법도 동일하다. 상세 정보는 Board component에서 props.location.state에서 받아온다.

 

BoardDetail.js

import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import Reply from './Reply';
import { withStyles } from '@material-ui/core/styles';
import myFireBase from '../../config/MyFireBase'
import { Redirect } from 'react-router-dom';
// Storage and Database from firebase
const storageRef = (new myFireBase).storageRef;
const databaseRef = (new myFireBase).databaseRef;
const styles = () => ({
    boardTopBackground: {
        zIndex: 1,
        backgroundColor: 'rgba(0, 0, 0, 0.5)',
        width: '100%',
        height: '12rem',
        position: 'absolute'
    },
    boardTop: {
        height: '12em',
        width: '100%',
        position: 'relative',
    },
    boardTopText: {
        zIndex: 2,
        position: 'absolute',
        bottom: '3rem',
        left: '3rem',
        fontSize: "2rem",
        fontWeight: 'bold',
        color: 'white',
    },
    boardTopImg: {
        height: '12em',
        width: '100%',
        objectFit: 'cover',
        position: 'relative'
    },
    boardDetailMain: {
        height: "auto",
        marginTop: "1rem",
        marginBottom: "3rem",
        border: "1px solid black"
    },
    boardDetailTitle: {
        height: "3rem",
        lineHeight: "3rem",
        paddingLeft: "1rem",
        boxSizing: "border-box",
        float: "left"
    },
    boardDetailDate: {
        height: "3rem",
        float: "right",
        marginRight: "1rem",
        lineHeight: "3rem",
        textAlign: "center"
    },
    boardDetailCreatedby: {
        height: "2rem",
        lineHeight: "2rem",
        padding: "1rem",
        boxSizing: "border-box",
        borderTop: "1px Solid black"
    },
    boardDetailContent: {
        height: "29rem",
        padding: "2rem"
    },
    boardDetailReplyHitNum: {
        height: "2rem",
        lineHeight: "2rem",
        paddingLeft: "1rem",
        boxSizing: "border-box"
    },
    boardDetailReplyNum: {
        borderRight: "1px solid black",
        width: "7rem",
        float: "left",
        textAlign: "center"
    },
    boardDetailHitNum: {
        width: "7rem",
        float: "left",
        textAlign: "center"
    },
    boardDetailReply: {
        backgroundColor: "#eeeeee",
        height: "auto",
        margin: "1rem",
        padding: "1rem",
        boxSizing: "border-box"
    },
    boardDetailReplySubmitContent: {
        width: "90%",
        height: "5rem",
        float: "left",
        marginTop: '1rem',
        padding: "0.5rem",
        backgroundColor: "white",
        boxSizing: "border-box",
        fontSize: "1rem",
        outline: "0px",
        color: "#494949",
        fontWeight: "normal",
        '@media (max-width:960px)': {
            width: '70%',
        }
    },
    boardDetailReplySubmitButton: {
        width: "10%",
        height: "5rem",
        float: "left",
        marginTop: '1rem',
        paddingLeft: "1rem",
        boxSizing: "border-box",
        '@media (max-width:960px)': {
            width: '30%',
        }
    }
})

class BoardDetail extends Component {
    // Init
    constructor(props) {
        super(props);
        this.state = {
            imgSrcBoardTop: '',
            replyData: {},
            replySubmitContent: '',
            replyNum: 0
        }
    }
    componentDidMount() {
        this.getImage();
        this.getReplyData();
    }
    // Get Image
    getImage() {
        storageRef.child('boardtop.jpg').getDownloadURL().then((url) => {
            this.setState({ imgSrcBoardTop: url });
        }).catch((error) => {
        })
    }
    // Get Reply Data
    getReplyData() {
        databaseRef.child('reply/').once('value').then(data=>{
            this.setState({ replyData: this.filterReplyData(data.val(), this.props.location.state.id) })
        })
    }
    // Post Reply Data
    postReplyData(reply) {
        let postData ={
            boardId: reply.boardId,
            content: reply.content,
            name: reply.name,
            date: reply.date
        }
        let postKey =  databaseRef.child('reply/').push().key;
        let updates = {};
        updates['/reply/'+postKey] = postData;
        databaseRef.update(updates);
        this.getReplyData();
    }
    // Filter Reply Data with Board ID
    filterReplyData = (totalReplyData, boardId) => {
        let filteredReplyData = [];
        let replyNum = 0;
        Object.keys(totalReplyData).map((idx) => {
            const r = totalReplyData[idx];
            if(r.boardId === boardId){
                filteredReplyData.push(r);
                replyNum += 1;
            }
        })
        this.setState({replyNum: replyNum});
        return filteredReplyData;
    }
    // Submit Reply
    handleSubmit = () => {
        const reply = {
            boardId: this.props.location.state.id,
            content: this.state.replySubmitContent,
            name: 'Unknown User',
            date: (new Date()).getFullYear() +'-'+(new Date()).getMonth() + '-' + (new Date()).getDate()
        }
        if (!reply.content || !reply.boardId) {
            alert('Write Reply Content');
            return;
        }
        this.postReplyData(reply);
        this.setState({replySubmitContent:''});
        this.setState({replyNum:this.state.replyNum+1});
    }
    // Handle Change Value
    handleValueChange = (e) => {
        let nextState = {};
        nextState[e.target.name] = e.target.value;
        this.setState(nextState);
    }
    render() {
        // Set classes
        const { classes } = this.props;
        if (this.props.location.state === undefined) {
            return (
                <Redirect to="/main/board" />
            )
        } else {
            // Return
            return (
                <div>
                    <div className={classes.boardTop}>
                        <div className={classes.boardTopBackground}></div>
                        <img className={classes.boardTopImg} src={this.state.imgSrcBoardTop} alt="boardtop" />
                        <div className={classes.boardTopText}>
                            Notices of Sallab
                        </div>
                    </div>
                    <div style={{ 'margin-left': '5%', 'margin-right': '5%' }}>
                        <Link to="/main/board">
                            <button className="lunchBox-btn-rec-line" style={{ "width": "5rem", "margin-top": "1rem" }}>
                                Back
                            </button>
                        </Link>
                        <div className={classes.boardDetailMain}>
                            <div style={{ "height": "3rem" }}>
                                <div className={classes.boardDetailTitle}>
                                    <div className="lunchBox-textField-black-title">
                                        Title
                                    </div>
                                </div>
                                <div className={classes.boardDetailDate}>
                                    <div className="lunchBox-textField-black-content-small">
                                        {this.props.location.state.createdDate}
                                    </div>
                                </div>
                            </div>
                            <div className={classes.boardDetailCreatedby}>
                                <div className="lunchBox-textField-black-content-small">
                                    {this.props.location.state.createdby}
                                </div>
                            </div>
                            <div className={classes.boardDetailContent}>
                                <div className="lunchBox-textField-black-content-small">
                                    {this.props.location.state.content}
                                </div>
                            </div>
                            <div className={classes.boardDetailReplyHitNum}>
                                <div className={classes.boardDetailReplyNum}>
                                    <div className="lunchBox-textField-black-content-small">Reply ({this.state.replyNum})</div>
                                </div>
                                <div className={classes.boardDetailHitNum}>
                                    <div className="lunchBox-textField-black-content-small">Hit ({this.props.location.state.hit})</div>
                                </div>
                            </div>
                            <div className={classes.boardDetailReply}>
                                <div>
                                    {Object.keys((this.state.replyData)).map((idx) => {
                                        const r = this.state.replyData[idx];
                                        return (
                                            <Reply name={r.name} date={r.date} content={r.content} />
                                        )
                                    })}
                                </div>
                                <div style={{ "overflow": "auto" }}>
                                    <input value={this.state.replySubmitContent} name="replySubmitContent" onChange={this.handleValueChange} className={classes.boardDetailReplySubmitContent} />
                                    <div className={classes.boardDetailReplySubmitButton}>
                                        <button className="lunchBox-btn-rec-line" style={{ "width": "100%", "height": "100%" }} onClick={this.handleSubmit}>Submit</button>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                    <p style={{ "display": "none" }}>{this.props.location.state.id}</p>
                </div>
            )
        }

    }
}

export default withStyles(styles)(BoardDetail);

 

Firebase에서 댓글 정보를 배열 형태의 json으로 받아온다. 이때 모든 reply 데이터를 받아와 boarId가 일치하는 reply 데이터만 보여주기 위해 filterReplyData 메소드를 작성하였다. 이렇게 reply 데이터를 필터링한 후에는 reply 데이터를 보여주는데 이때 같은 형태의 데이터를 반복적으로 보여주어야 하므로 Reply component를 작성하여 이용하였다.

 

Reply.js

import React, { Component } from 'react';
import { withStyles } from '@material-ui/core/styles';
const styles = () => ({
    root: {
      backgroundColor: "#eeeeee",
      height: '6em',
      marginTop: '1rem',
      borderBottom: '0.05rem dotted #cccccc',
      width: '100%',
    },
    name: {
      height: '2rem',
      float: 'left'
    },
    date: {
      height: '2rem',
      float: 'right'
    },
    content: {
      clear: 'both'
    }
})
class Reply extends Component {
    render() {
        // Set classes
        const { classes } = this.props;
        // Return
        return (
            <div className={classes.root}>
              <div>
                <div className={classes.name}>
                  <div className="lunchBox-textField-black-content-small" style={{"height":"100%"}}>
                    {this.props.name}
                  </div>
                </div>
                <div className={classes.date}>
                  <div className="lunchBox-textField-black-content-small" style={{"height":"100%"}}>
                    {this.props.date}
                  </div>
                </div>
              </div>
              <div className="lunchBox-textField-black-content-small" style={{"height":"100%"}}>
                <div className={classes.content}>
                  {this.props.content}
                </div>
              </div>
            </div>
        );
    };
}
export default withStyles(styles)(Reply);

 

Reply 작성

댓글을 작성하면 댓글의 내용을 state에 저장해야한다. 이를 위해 input 태그의 onChange 이벤트에 handleValueChange 메서드를 작성하였다. 이 메소드에서는 input 태그의 값이 변할 때마다 input 태그의  value를 replySubmitContent에 저장한다. 댓글 작성 후에 Submit 버튼을 클릭하면 댓글을 제출하기 위해 button 태그의 onClick 이벤에 handleSubmit 메소드를 작성하였다. 이 메소드에서는 현재 boardId, 댓글 내용, 작성자, 날짜를 Firebase database에 입력한다. 데이터를 입력하는 방법은 Firebase의 문서를 참고하였다.

 

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

 

Database reference에서 update 메소드를 호출하는데 이때 새롭게 생성한 key값과 입력하고자 하는 데이터를 이용하여 데이터 형태를 맞추고 이를 update 메소드의 인자로 전달한다.

'개인 개발 프로젝트 > Lunch Box 앱' 카테고리의 다른 글

[Lunch Box] 13. 로그인 화면  (0) 2019.10.14
[Lunch Box] 12. 주문 화면  (0) 2019.10.14
[Lunch Box] 10. 공지 화면  (0) 2019.10.13
[Lunch Box] 9. 메뉴 화면  (0) 2019.10.09
[Lunch Box] 8. 홈 화면  (0) 2019.10.08