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;
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;
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에 이런식으로 추가!
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;
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;
/* 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;
/* 줄어든다고 버튼이 너무 줄어들지 않게 */
}