Aiko掲示板(Redux、Redux-Persist)

109106 ワード

私は李徳思が嫌いです。アクションを復元しても作成しても、やるべきことが多すぎます。私のREDEXショップも自分で作ったものではありません。


まず、Ridexで掲示板を実現し、画面内でしかプレイできませんでした。


輸入商団
//~~と書かれている部分がpage名なので、よく見てみましょう.

// 게시판
import * as React from 'react';
import axios from 'axios';
import { useState, useEffect } from 'react';
import Button from '@material-ui/core/Button';
import router from 'next/router';
import Link from 'next/link';
import { useSelector, useDispatch } from 'react-redux';
import {selectRow} from '../_redux/boardReducer';
import styles from '../styles/Board.module.css';
import FormControl from '@material-ui/core/FormControl';
import Select from '@material-ui/core/Select';
import { makeStyles } from '@material-ui/core/styles';
import InputLabel from '@material-ui/core/InputLabel';

const handleLogin = () => {
    const url = '/api/notice-board/files';
    const config = {
        header: {
            'content-type': 'application/json',
        },
    };
    (async () => {
        try {
            await get(url, config);
        } catch (err) {
            console.log(err);
        }
    })();
};

const useStyles = makeStyles((theme) => ({
  root: {
    '& > *': {
      marginTop: theme.spacing(2),
    },
  },
}));


export default function board() {

//for pagination
const [posts, setPosts] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [postsPerPage, setPostPerPage] = useState(10);

useEffect(() => {
    const fetchPosts = async () => {
    const res = await axios.get('http://jsonplaceholder.typicode.com/posts');
    setPosts(res.data);
    }
    fetchPosts();
}, []);

console.log(posts)

//get current posts
const indexOfLastPost = currentPage * postsPerPage;
const indexOfFirstPost = indexOfLastPost - postsPerPage;
const currentPosts = posts.slice(indexOfFirstPost, indexOfLastPost);

const {inputData} = useSelector((state) => state.boardReducer);
const {lastId} = useSelector((state) => state.boardReducer);

const dispatch = useDispatch();

const selectContent = (id) => {
    dispatch(selectRow(id))
};

const handleChange = (e) => {
        setRow(e.target.value)
};

const [row, setRow] = useState('');

const classes = useStyles();

return (
    <>

    <div className={styles.desc}>
        <h2 className={styles.aikoBoard}>AIKO notice board</h2>

        <div style={{marginRight:'30px'}}>
        <FormControl variant="outlined" className={styles.formControl}>
            <InputLabel htmlFor="outlined-age-native-simple">Rows</InputLabel>
            <Select
            native
            value={row}
            onChange={handleChange}
            label="Age"
            >
            <option aria-label="None" value="" />
            <option value={10}>10</option>
            <option value={20}>20</option>
            <option value={30}>30</option>
            </Select>
        </FormControl>
        </div>
    </div>
    
    <div>
        <table className={styles.table}>
            <thead className={styles.thead}>
                <tr>
                    <th className={styles.th} style={{width:'7%'}}>No.</th>
                    <th className={styles.th} style={{width:'50%', textAlign:'left'}}>Title</th>
                    <th className={styles.th} style={{width:'20%', textAlign:'left'}}>Posted by</th>
                    <th className={styles.th} style={{width:'15%', textAlign:'left'}}>Date</th>
                </tr>
            </thead>

            <tbody className={styles.tbody}>
                <tr>
                    <td></td>
                    <td></td>
                </tr>
                {
                    lastId !== 0 ?
                    inputData.map(rowData => (
                        rowData.id !== '' &&
                    <tr>
                        <td className={styles.td} onClick={() => selectContent(rowData.id)}><Link href='/innerPost'><a>{rowData.id}</a></Link></td>
                        <td className={styles.td} style={{textAlign:'left'}} onClick={() => selectContent(rowData.id)}><Link href='/innerPost'><a>{rowData.title}</a></Link></td>
                        <td className={styles.td} style={{textAlign:'left'}}>{rowData.name}</td>
                        <td className={styles.td} style={{textAlign:'left'}}>{rowData.date}</td>
                    </tr>
                    )) :
                    <tr>
                        <td className={styles.td}></td>
                        <td className={styles.td}>작성된 글이 없습니다.</td>
                    </tr>
                }   
            </tbody>
        </table>
    </div>


    <div className={styles.postButtonContainer}>
        <Button variant="contained" color="primary" style={{
          width:'100px', height:'50px', borderRadius:'15px'}}
        onClick={()=>{router.push('/writePost')}}>
            NEW POST
        </Button>
    </div>

    <div style={{ height: 300, width: '30%' }}>
        <div onClick={handleLogin}> 파일 다운로드 테스트 </div>
    </div>

    </>
);
}

//글 작성

import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import styles from '../styles/WritePost.module.css';
import {useState} from 'react';
import Button from '@material-ui/core/Button';
import axios from 'axios';
import {useDispatch} from 'react-redux';
import {dataSave} from '../_redux/boardReducer';
import router from 'next/router';


export default function writePost() {

    const [title, setTitle] = useState('');
    const [content, setContent] = useState('');
    const [name, setName] = useState('');
    const [file, setFile] = useState(null);

  
    const titleChange = (e) => {
      setTitle(e.target.value);
    };
    
    const contentChange = (e) => {
      setContent(e.target.value);
    };

    const handleFile = (e) => {
      setFile(e.target.files);
    };

    const nameChange = (e) => {
      setName(e.target.value);
    };

    const dispatch = useDispatch();

    const upload = () => {
      const formData = new FormData();
      const url = '/api/notice-board/write';
      formData.append("title", title);
      formData.append("content", content);
      for (let i=0; i<3; i++) {
      formData.append("file", file[i]);
      }
      const config = {
        headers: {
          "content-type" : "multipart/form-data"
        },
      };
      axios.post(url, formData, config)
        .then((response) => {
          console.log(response);
        })
        .catch((error) => {
          console.log(error)
        })
    };

    const date = new Date();

    const onSave = () => {
      const _inputData = {
        id: '',
        title:title,
        content: content,
        name: name,
        date: Date.toLocaleString()
      }
      dispatch(dataSave(_inputData))
      setTitle('')
      setContent('')
      setName('')
      router.push('/board')
      upload();
    }
    
    return (
  <>

<div className={styles.writeBoard}>

  <h2 style={{color:'#3F51B5', paddingTop:'20px', paddingLeft:'15%'}}>New Post</h2>

  <div className={styles.titleName} style={{marginBottom:'20px'}}>
  <div style={{width:'50%'}}>
      <h4 style={{color:'#656565'}}>Title</h4>
      <input className={styles.titleInput} type="text" value={title} placeholder="제목을 입력해주세요" onChange={titleChange}/>
  </div>
  <div style={{width:'20%'}}>
      <h4 style={{color:'#656565'}}>Name</h4>
      <input className={styles.nameInput} type="text" value={name} placeholder="이름을 입력해주세요" onChange={nameChange}/>
  </div>
  </div>

  <div className={styles.contentBox}>
    <div style={{width:'70%'}}>
      <h4 style={{color:'#656565'}}>Content</h4>
      <textarea className={styles.contentInput} type="text" value={content} placeholder="내용을 입력해주세요" onChange={contentChange} />

      <div className={styles.fileSubmit}>
      <label className={styles.fileLabel} onChange={handleFile}>
        <input type="file" multiple style={{display:'none'}}/>
        + Attach File
      </label>
        <Button variant="contained" color="primary" style={{
          width:'100px', height:'50px', borderRadius:'15px'}} onClick={onSave}>
            SUBMIT
        </Button>
      </div>
    </div>
  </div>

</div>



  </>
    )
}
//게시글 확인
import react from 'react';
import styles from '../styles/innerPost.module.css';
import { makeStyles } from '@material-ui/core/styles';
import Paper from '@material-ui/core/Paper';
import Grid from '@material-ui/core/Grid';
import Button from '@material-ui/core/Button';
import { useState } from 'react';
import {useDispatch , useSelector} from 'react-redux';
import router from 'next/router';
import {editContent, removeContent} from '../_redux/boardReducer.js';
import axios from 'axios';


const useStyles = makeStyles((theme) => ({
  root: {
    flexGrow: 1,
  },
  paper: {
    padding: theme.spacing(2),
    textAlign: 'center',
    color: theme.palette.text.secondary,
  },
}));

export default function innerPost() {

  const { selectRowData } = useSelector(state => state.boardReducer);

  const [title, setTitle] = useState(selectRowData.title);
  const [content, setContent] = useState(selectRowData.content);
  const [name, setName] = useState(selectRowData.name);
  const [date, setDate] = useState(selectRowData.date);

  const handleTitle = (e) => {
    setTitle(e.target.value);
  }

  const handleContent = (e) => {
    setContent(e.target.value);
  }

  const handleName = (e) => {
    setName(e.target.value);
  }

  const dispatch = useDispatch();

  const onChange = () => {
    const _inputData = {
      id: selectRowData.id,
      title:title,
      content: content,
      name: name,
      date : date
    }
    const url = '/api/notice-board/update-article'
    const data = {
      'num': selectRowData.id,
      'title': JSON.stringify(title),
      'content': JSON.stringify(content)
    }
    const config = {
      headers: {
        "content-type" : 'application/json'
      }
    };
    axios.post(url, data, config)
    .then((response)=> {
      console.log(selectRowData.id)
    })
    .catch((error) => {
      console.log(error)
    });
    dispatch(editContent(_inputData))
    setTitle('');
    setContent('');
    setName('');
    router.push('/board');
  }

  const onRemove = () => {
    dispatch(removeContent(selectRowData.id))
    setTitle('');
    setContent('');
    setName('');
    const url = "/api/notice-board/delete-article";
    const data = {
      'num' : selectRowData.id
    }
    const config ={
      headers: {
        'content-type' : 'application/json'
      }
    };
    axios.post(url, data, config)
    .then((response)=> {
      console.log(data)
    })
    .catch((error)=> {
      console.log(error)
    });
    router.push('/board');
  }


    const classes = useStyles();

    return (
        <>

        <div className={styles.outerContainer}>

        <div className={styles.titleName}>
          <input className={styles.titleInput} onChange={handleTitle} value={title}/>
          <p style={{fontSize:'17px', color:'#6F6A6A'}}>Posted by {name}, {date}</p>
        </div>

        <div className={styles.contentArea}>
          <textarea className={styles.contentInput} value={content} onChange={handleContent} />
        </div>


      <div className={styles.reviseDelete}>

        <Button variant="contained" color="primary"style={{
          width:'100px', height:'50px', borderRadius:'15px', marginLeft:'15%' , backgroundColor:'#969696'}}
        onClick={()=>{router.push('/board')}}>
            LIST
        </Button>

          <div className={styles.align} style={{marginRight:'10%'}}>
          <Button variant="contained" color="primary" style={{
            width:'100px', height:'50px', borderRadius:'15px'}}
          onClick={onChange}>
              REVISE
          </Button>

          <Button variant="contained" color="primary" style={{
            width:'100px', height:'50px', borderRadius:'15px', marginLeft:'10px', backgroundColor:'#D93D3D'}}
          onClick={onRemove}>
              DELETE
          </Button>
          </div>
        </div>


        <div className={styles.anotherPost} style={{marginTop:'15px'}}>
          
            <div className={styles.previousPost}>
          <Button size="small" className={classes.margin} style={{width:'20%'}}>
          이전 글 보기
          </Button>

          <p style={{fontSize:'12px', color:'#9a9a9a', cursor:'pointer'}} onClick={function(){
            alert('아직 기능 구현 중입니다.')
          }}>사내식당 공지) 202111월 건강한 식생활을 위한 채식주의 방침</p>
            </div>

          <div className={styles.nextPost}>
            <Button size="small" className={classes.margin} style={{width:'20%'}}>
            다음 글 보기
            </Button>

          <p style={{fontSize:'12px', color:'#9a9a9a', cursor:'pointer'}} onClick={function(){
            alert('아직 기능 구현 중입니다.')
          }}>2021년도 Aiko 프로젝트 감사 결과</p>
          </div>

        </div>

        </div>

        </>
    );
}
// REDUX, REDUCER

const SAVE = 'DATA_SAVE';
const SELECT = 'DATA_SELECT';
const EDIT = 'DATA_EDIT';
const DELETE = 'DATA_DELETE';

const date = new Date();

export const dataSave = (inputData) => ({
    type: SAVE,
    inputData: {
        id: inputData.id,
        title: inputData.title,
        content: inputData.content,
        name: inputData.name,
        date: date.toLocaleString()
    }
});

export const selectRow = (id) => ({
    type: SELECT,
    inputData: {
        id: id,
    }
});

export const editContent = (inputData) => ({
    type: EDIT,
    inputData: {
        id: inputData.id,
        title: inputData.title,
        content:inputData.content,
        name: inputData.name,
        date: inputData.date
    }
});

export const removeContent = (id) => ({
    type: DELETE,
    inputData: {
        id : id
    }
});



const initialState = {
    lastId: 0,
    inputData: [
        {
            id:'',
            title:'',
            content:'',
            name:'',
            date: date.toLocaleString()
        }
    ],
    selectRowData: {}
};

export default function boardReducer(state = initialState, action) {
    switch(action.type) {
        case SAVE:
            return {
            lastId: state.lastId + 1,
            inputData: state.inputData.concat({
                ...action.inputData,
                id: state.lastId + 1,
            })
        }
        case SELECT:
            return {
                ...state,
                selectRowData: state.inputData.find(row => row.id === action.inputData.id)
            }
        case EDIT:
            return {
                ...state,
                inputData: state.inputData.map(row => row.id === action.inputData.id ?
                    {...action.inputData} : row
                    ),
                    selectRowData: {}
            }
        case DELETE:
            return {
                lastId: state.lastId === action.inputData.id ? state.lastId -1 : state.lastId,
                inputData: state.inputData.filter(row=>
                    row.id !== action.inputData.id
                    ),
                    selectRowData: {}
            }
        default:
            return state;
    }
};

逆伝播接続