[감정 일기장 만들기] home페이지

미아·2023년 1월 9일
0

REACT

목록 보기
32/41

헤더


1) 현재 년 , 월
2) < 왼쪽 버튼
3) 오른쪽 버튼

import React, { useState } from "react";
import MyHeader from "../components/MyHeader";
import MyButton from "../components/Mybutton";
const Home = () => {
  const [currDate, setCurrDate] = useState(new Date());
  const headText = `${currDate.getFullYear()}년 ${currDate.getMonth() + 1}월`;

  const increaseMonth = () => {
    //new Date로 새로운 데이트 객체 생성
    setCurrDate(
      new Date(
        currDate.getFullYear(),
        currDate.getMonth() + 1,
        currDate.getDate()
      )
    );
  };

  const decreaseMonth = () => {
    //new Date로 새로운 데이트 객체 생성
    setCurrDate(
      new Date(
        currDate.getFullYear(),
        currDate.getMonth() - 1,
        currDate.getDate()
      )
    );
  };

  return (
    <div>
      <MyHeader
        headText={headText}
        leftChild={<MyButton text={"<"} onClick={decreaseMonth} />}
        rightChild={<MyButton text={">"} onClick={increaseMonth} />}
      />
    </div>
  );
};
export default Home;

dummydata만들고 => context 넘겨주기

import React, { useReducer, useRef } from "react";
import "./App.css";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Home from "./pages/Home";
import New from "./pages/New";
import Edit from "./pages/Edit";
import Diary from "./pages/Diary";

const reducer = (state, action) => {
  let newState = [];
  switch (action.type) {
    case "INIT": {
      return action.data;
    }
    case "CREATE": {
      newState = [action.data, ...state];
      break;
    }
    case "REMOVE": {
      newState = state.filter((it) => it.id !== action.targetId);
      break;
    }
    case "EDIT": {
      newState = state.map((it) =>
        it.id === action.data.id ? { ...action.data } : it
      );
      break;
    }
    default:
      return state;
  }
  return newState;
};
// CreateContext - data 공급
export const DiaryStateContext = React.createContext();
// onCreate, onRemove, onEdit 전달
export const DiaryDispatchContext = React.createContext();

//1️⃣dummyData 만들기
const dummyData = [
  {
    id: 1,
    emotion: 1,
    content: "오늘의 일기 1번",
    // 구하는 가장 쉬운 방법은 console.log(new Date().getTime())
    date: 1673224084848,
  },
  {
    id: 2,
    emotion: 2,
    content: "오늘의 일기 2번",
    date: 1673224084849,
  },
  {
    id: 3,
    emotion: 3,
    content: "오늘의 일기 3번",
    date: 1673224084850,
  },
  {
    id: 4,
    emotion: 4,
    content: "오늘의 일기 4번",
    date: 1673224084851,
  },
  {
    id: 5,
    emotion: 5,
    content: "오늘의 일기 5번",
    date: 1673224084852,
  },
];

function App() {
  // 2️⃣ useReducer 초기값으로 dummyData 넣기
  const [data, dispatch] = useReducer(reducer, dummyData);
  // 이건 시간 ms로 구하는법
  console.log(new Date().getTime());

  /*dispatch 함수 필요한경우*/
  //CREATE
  const dataId = useRef(0); //id는 useRef 훅으로 만든다
  const onCreate = (date, content, emotion) => {
    dispatch({
      type: "CREATE",
      data: {
        //받아야 하는 항목 : date, content, emotion, id!(여기는 작성자가 없다.)
        date: new Date(date).getTime(), // 커서 올렸을때 보라색으로 뜨면 () 포함임
        content,
        emotion,
        id: dataId.current,
      },
    });
    dataId.current += 1;
  };
  //REMOVE
  const onRemove = (targetId) => {
    dispatch({
      type: "REMOVE",
      targetId,
    });
  };
  //EDIT
  const onEdit = (date, content, emotion, targetId) => {
    dispatch({
      type: "EDIT",
      data: {
        id: targetId, // 얘 빼고 다 바꾸는거
        date: new Date(date).getTime(),
        content,
        emotion,
      },
    });
  };

  return (
    <DiaryStateContext.Provider value={data}>
      <DiaryDispatchContext.Provider value={{ onCreate, onEdit, onRemove }}>
        <BrowserRouter>
          <div className="App">
            <Routes>
              <Route path="/" element={<Home />} />
              <Route path="/new" element={<New />} />
              <Route path="/diary/:id" element={<Diary />} />
              <Route path="/edit" element={<Edit />} />
            </Routes>
          </div>
        </BrowserRouter>
      </DiaryDispatchContext.Provider>
    </DiaryStateContext.Provider>
  );
}

export default App;

home.js에 현재 달의 일기만 보여주도록 하는 로직 작성

import React, { useEffect, useContext, useState } from "react";
import MyHeader from "../components/MyHeader";
import MyButton from "../components/Mybutton";
import { DiaryStateContext } from "../App";

const Home = () => {
  //현재 들어와있는 월의 일기만 추려내서 보여주기
  const [data, setData] = useState([]);
  const diaryList = useContext(DiaryStateContext);

  const [currDate, setCurrDate] = useState(new Date());
  const headText = `${currDate.getFullYear()}년 ${currDate.getMonth() + 1}월`;

  useEffect(() => {
    //만약 일기가 비어있는 상황이라면 보여줄 필요없으니
    if (diaryList.length >= 1) {
      const firstday = new Date(
        currDate.getFullYear(),
        currDate.getMonth(),
        1
      ).getTime();

      const lastday = new Date(
        currDate.getFullYear(),
        currDate.getMonth() + 1,
        0
      ).getTime();

      //filter로 걸러주기
      setData(
        diaryList.filter((it) => firstday <= it.date && lastday >= it.date)
      );
      //useEffect는 []배열 안의 값이 바뀌어야 실행되기때문에, diaryList(수정하거나 삭제 등)이 바뀌어도 실행되어야한다.
    }
  }, [diaryList, currDate]);

  //useEffect로 확인하기(data의 월이 같을때만 뽑는지 확인), 없는 달은 빈 배열 나온다
  useEffect(() => {
    console.log(data);
  }, [data]);

  const increaseMonth = () => {
    //new Date로 새로운 데이트 객체 생성
    setCurrDate(
      new Date(
        currDate.getFullYear(),
        currDate.getMonth() + 1,
        currDate.getDate()
      )
    );
  };

  const decreaseMonth = () => {
    //new Date로 새로운 데이트 객체 생성
    setCurrDate(
      new Date(
        currDate.getFullYear(),
        currDate.getMonth() - 1,
        currDate.getDate()
      )
    );
  };

  return (
    <div>
      <MyHeader
        headText={headText}
        leftChild={<MyButton text={"<"} onClick={decreaseMonth} />}
        rightChild={<MyButton text={">"} onClick={increaseMonth} />}
      />
    </div>
  );
};
export default Home;

별도의 컴포넌트로 분할해서 일기 리스트 보여주기

(DiaryList.js)

const DiaryList = ({ diaryList }) => {
  return (
    <div>
      {diaryList.map((it) => (
        <div key={it.id}>{it.content}</div>
      ))}
    </div>
  );
};
DiaryList.defaultProps = {
  diaryList: [],
};
export default DiaryList;


-> home.js에 이런식으로 추가!

필터 만들기(DiaryList.js) - 최신순/ 오래된순

import React, { useState } from "react";
const sortOptionList = [
  { value: "latest", name: "최신순" },
  { value: "oldest", name: "오래된순" },
];
// option 다시 보기
const ControlMenu = ({ value, onChange, optionList }) => {
  return (
    <select value={value} onChange={(e) => onChange(e.target.value)}>
      {/* it은 prop으로 받은 optionList의 첫번째 객체 (즉 latest / name)*/}
      {optionList.map((it, idx) => (
        <option key={idx} value={it.value}>
          {it.name}
        </option>
      ))}
      {/* select태그의 onChange는 바뀔때 value를 받음, onChange는 setSortType을 prop으로받기때문에 선택한걸로 보이는것 */}
    </select>
  );
};
const DiaryList = ({ diaryList }) => {
  //filter 정렬
  const [sortType, setSortType] = useState("latest");
  //옵션 선택 시 일기가 그 순으로 정렬되어야함 / 단 diaryList원본 배열이 바뀌면 안되므로 복사해야함
  const getProcessDiaryList = () => {
    //배열에 객체로 들어가있는 애들은 그냥 정렬하면 정렬이 안됨 - sort써줘야함
    // sort : return 1이상이면 b먼저 오고 -1이하이면 a먼저옴 0이면 그대로 놔둠
    const compare = (a, b) => {
      if (sortType === "latest") {
        return parseInt(b.date) - parseInt(a.date);
      } else {
        return parseInt(a.date) - parseInt(b.date);
      }
    };
    //깊은 복사
    const copyList = JSON.parse(JSON.stringify(diaryList)); //배열을 json화해서 문자열로 바꾼다 => JSON.PARSE는 다시 배열로
    const sortedList = copyList.sort(compare);
    return sortedList;
  };
  return (
    <div>
      <ControlMenu
        value={sortType}
        onChange={setSortType}
        optionList={sortOptionList}
      />
      {/* 이제 여기는 sort된 애들을 돌려야하므로 diaryList에서 getProcessDiaryList()로 바꿈 */}
      {getProcessDiaryList().map((it) => (
        <div key={it.id}>{it.content}</div>
      ))}
    </div>
  );
};
DiaryList.defaultProps = {
  diaryList: [],
};
export default DiaryList;

같은 기능인 좋은감정 / 안좋은 감정 필터 만들기

import React, { useState } from "react";
// 최신순 , 오래된순 옵션
const sortOptionList = [
  { value: "latest", name: "최신순" },
  { value: "oldest", name: "오래된순" },
];
// 2️⃣좋은, 안좋은 감정순 옵션
const filterOptionList = [
  { value: "all", name: "전부다" },
  { value: "good", name: "좋은 감정만" },
  { value: "bad", name: "안좋은 감정만" },
];
const ControlMenu = ({ value, onChange, optionList }) => {
  return (
    <select value={value} onChange={(e) => onChange(e.target.value)}>
      {optionList.map((it, idx) => (
        <option key={idx} value={it.value}>
          {it.name}
        </option>
      ))}
    </select>
  );
};
const DiaryList = ({ diaryList }) => {
  const [sortType, setSortType] = useState("latest");
  // 1️⃣두번째 필터 만들기(모든 감정이 기본값)
  const [filter, setFilter] = useState("all");

  //만약 들어가는 state 가 헷갈리면, 기본값 설정한걸 보자
  //6️⃣filterCallback => 좋은 감정 안좋은 감정 필터링하는 함수
  const filterCallback = (item) => {
    if (filter === "good") {
      return parseInt(item.emotion) <= 3;
    } else {
      return parseInt(item.emotion) > 3;
    }
  };
  const getProcessDiaryList = () => {
    const compare = (a, b) => {
      if (sortType === "latest") {
        return parseInt(b.date) - parseInt(a.date);
      } else {
        return parseInt(a.date) - parseInt(b.date);
      }
    };
    //깊은 복사
    const copyList = JSON.parse(JSON.stringify(diaryList)); //배열을 json화해서 문자열로 바꾼다 => JSON.PARSE는 다시 배열로
    //5️⃣ 필터링 조건 걸기(filter는 useState의 filter임) 조건이 기니까 함수 만들기
    const filteredList =
      filter === "all" ? copyList : copyList.filter((it) => filterCallback(it));
    //7️⃣ 전체 조건 걸려있는 filteredList를 sort에 걸어줌
    const sortedList = filteredList.sort(compare);
    return sortedList;
  };

  return (
    <div>
      <ControlMenu
        value={sortType}
        onChange={setSortType}
        optionList={sortOptionList}
      />
      {/* 3️⃣ */}
      <ControlMenu
        value={filter}
        onChange={setFilter}
        optionList={filterOptionList}
      />
      {/* 4️⃣ 어떤 감정이 좋은 감정인지 모르므로 it.emotion도 전달! */}
      {getProcessDiaryList().map((it) => (
        <div key={it.id}>
          {it.content} {it.emotion}
        </div>
      ))}
    </div>
  );
};
DiaryList.defaultProps = {
  diaryList: [],
};
export default DiaryList;

새일기쓰기 버튼 구현/useNavigate이용한 페이지 이동구현

import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import Mybutton from "./Mybutton";
// 최신순 , 오래된순 옵션
const sortOptionList = [
  { value: "latest", name: "최신순" },
  { value: "oldest", name: "오래된순" },
];
// 좋은, 안좋은 감정순 옵션
const filterOptionList = [
  { value: "all", name: "전부다" },
  { value: "good", name: "좋은 감정만" },
  { value: "bad", name: "안좋은 감정만" },
];
const ControlMenu = ({ value, onChange, optionList }) => {
  return (
    <select value={value} onChange={(e) => onChange(e.target.value)}>
      {optionList.map((it, idx) => (
        <option key={idx} value={it.value}>
          {it.name}
        </option>
      ))}
    </select>
  );
};
const DiaryList = ({ diaryList }) => {
  // 2️⃣ useNavigate 이용해서 페이지 이동 구현하기
  const navigate = useNavigate();
  const [sortType, setSortType] = useState("latest");
  // 두번째 필터 만들기(모든 감정이 기본값)
  const [filter, setFilter] = useState("all");

  //만약 들어가는 state 가 헷갈리면, 기본값 설정한걸 보자
  //filterCallback => 좋은 감정 안좋은 감정 필터링하는 함수
  const filterCallback = (item) => {
    if (filter === "good") {
      return parseInt(item.emotion) <= 3;
    } else {
      return parseInt(item.emotion) > 3;
    }
  };
  const getProcessDiaryList = () => {
    const compare = (a, b) => {
      if (sortType === "latest") {
        return parseInt(b.date) - parseInt(a.date);
      } else {
        return parseInt(a.date) - parseInt(b.date);
      }
    };
    //깊은 복사
    const copyList = JSON.parse(JSON.stringify(diaryList)); //배열을 json화해서 문자열로 바꾼다 => JSON.PARSE는 다시 배열로
    //필터링 조건 걸기(filter는 useState의 filter임) 조건이 기니까 함수 만들기
    const filteredList =
      filter === "all" ? copyList : copyList.filter((it) => filterCallback(it));
    //전체 조건 걸려있는 filteredList를 sort에 걸어줌
    const sortedList = filteredList.sort(compare);
    return sortedList;
  };

  return (
    <div>
      <ControlMenu
        value={sortType}
        onChange={setSortType}
        optionList={sortOptionList}
      />
      <ControlMenu
        value={filter}
        onChange={setFilter}
        optionList={filterOptionList}
      />
      {/* 1️⃣ mybutton 임포트해서 새일기쓰는 페이지로 매핑하기 */}
      <Mybutton
        type={"positive"}
        text={"새 일기 쓰기"}
        // useNavigate함수 쓰면 onclick시 navigate("쓴 경로")로 이동가능
        onClick={() => navigate("/new")}
      />
      {getProcessDiaryList().map((it) => (
        <div key={it.id}>
          {it.content} {it.emotion}
        </div>
      ))}
    </div>
  );
};
DiaryList.defaultProps = {
  diaryList: [],
};
export default DiaryList;

일기 아이템 컴포넌트로 만들기!


이렇게 세 부분으로 나눠 볼 수 있다.

import { useNavigate } from "react-router-dom";
import Mybutton from "./Mybutton";

const DiaryItem = ({ id, emotion, content, date }) => {
  // navigate 사용해서 페이지 이동하게
  const navigate = useNavigate();
  // onclick으로 navigate 걸어줘야함 , 함수로 만들어두기
  const goDetail = () => {
    navigate(`/diary/${id}`);
  };
  // goEdit(수정페이지로 이동)
  const goEdit = () => {
    navigate(`/edit/${id}`);
  };
  const strDate = new Date(parseInt(date)).toLocaleDateString();
  // dearme는 state 따로 받아서 해야할것같음
  return (
    <div className="DiaryItem">
      {/* 네모 안에 emotion 들어가게 하려고 */}
      <div
        onClick={goDetail}
        className={[
          "emotion_img_wrapper",
          `emotion_img_wrapper_${emotion}`,
        ].join(" ")}
      >
        <img src={process.env.PUBLIC_URL + `assets/emotion${emotion}.png`} />
      </div>
      <div onClick={goDetail} className="info_wrapper">
        <div className="diary_date">{strDate}</div>
        <div className="diary_content_preview">{content.slice(0, 25)}</div>
      </div>
      <div className="btn_wrapper">
        <Mybutton text={"수정하기"} onClick={goEdit} />
      </div>
    </div>
  );
};
export default DiaryItem;

CSS 스타일링

/* DiaryList */
.DiaryList .menu_wrapper{
    margin-top: 20px;
    margin-bottom: 20px;

    display: flex;
    justify-content: space-between;
}

.DiaryList .menu_wrapper .right_col{
    /* flex-grow:1은 남은 영역의 전체를 가지게 됨 */
    flex-grow: 1;
}
.DiaryList .menu_wrapper .right_col button{
    width: 100%;
}
.DiaryList .ControlMenu{
    margin-right: 10px;
    border: none;
    border-radius: 5px;
    background-color: #ececec;
    padding: 11px 20px;
    cursor: pointer;
    font-family: 'KyoboHand';
    font-size: 18px;
}
/* DiaryItem */
.DiaryItem{
    padding: 15px 0;
    border-bottom: 1px solid #e2e2e2;

    display: flex;
    justify-content: space-between;
}
/* 네모박스 */
.DiaryItem .emotion_img_wrapper{
    cursor: pointer;
    min-width: 120px;
    height: 80px;
    border-radius: 5px;
    display: flex;
    justify-content: center;
}
/* .DiaryItem .emotion_img_wrapper_1{
    background: #f19959;
} 
.DiaryItem .emotion_img_wrapper_2{
    background: #f3c65e;
} 
.DiaryItem .emotion_img_wrapper_3{
    background: #f7dad2;
} 
.DiaryItem .emotion_img_wrapper_4{
    background: #63afa0;
} 
.DiaryItem .emotion_img_wrapper_5{
    background: #e47566;
}  */
/* .DiaryItem .emotion_img_wrapper img{
    width: 50%;
} */

.DiaryItem .info_wrapper{
    cursor: pointer;
    flex-grow: 1;
    margin-left: 20px;
}
.DiaryItem .diary_date{
    font-weight: 900;
    font-size: 22px;
    margin-bottom: 5px;
}
.DiaryItem .diary_content_preview{
    font-size: 18px;
}
.DiaryItem .btn_wrapper{
    min-width: 70px;
    /* 줄어든다고 버튼이 너무 줄어들지 않게 */
}

전체 결과

profile
새로운 것은 언제나 재밌어 🎶

0개의 댓글