//app.js
import React from "react";
import Router from "./shared/Router";
const App = () => {
return <Router />;
};
export default App;
//Router.js
import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Detail from "../pages/Detail";
import Home from "../pages/Home";
const Router = () => {
return (
<BrowserRouter>
<Routes>
<Route path='/' element={<Home />} />
<Route path='/:id' element={<Detail />} />
</Routes>
</BrowserRouter>
);
};
export default Router;
//todos.js
// Action value
const ADD_TODO = "ADD_TODO";
const GET_TODO_BY_ID = "GET_TODO_BY_ID";
const DELETE_TODO = "DELETE_TODO";
const TOGGLE_STATUS_TODO = "TOGGLE_STATUS_TODO";
// Action Creator
// Todo를 추가하는 action creator
export const addTodo = (payload) => {
return {
type: ADD_TODO,
payload,
};
};
// Todo를 지우는 action creator
export const deleteTodo = (payload) => {
console.log("딜리트 페이로드", payload);
return {
type: DELETE_TODO,
id: payload,
};
};
// Todo를 isDone를 변경하는 action creator
export const toggleStatusTodo = (payload) => {
console.log("액션크리에이터에서 토글은", payload);
return {
type: TOGGLE_STATUS_TODO,
id: payload,
};
};
// 상세 페이지에서 특정 Todo만 조회하는 action creator
export const getTodoByID = (payload) => {
return {
type: GET_TODO_BY_ID,
payload,
};
};
// initial state
const initialState = {
todos: [
{
id: 1,
title: "리액트",
body: "리액트를 배워봅시다",
isDone: false,
},
],
todo: {
id: 0,
title: "",
body: "",
isDone: false,
},
};
const todos = (state = initialState, action) => {
switch (action.type) {
case ADD_TODO:
return {
...state,
// 기존 state.todos를 넣어주지 않았기 때문에 이니셜스테이츠를 대체해버림
todos: [...state.todos, { ...action.payload }],
};
case DELETE_TODO:
console.log("딜리트 디스패치 잘들어오는데", action);
return {
...state,
todos: state.todos.filter((todos) => todos.id !== parseInt(action.id)),
};
case TOGGLE_STATUS_TODO:
// return {
// ...state,
// todos: state.todos.map((todo) => {
// if (todo.id === action.payload) {
// return {
// ...todo,
// isDone: !todo.isDone,
// };
// } else {
// return todo;
// }
// }),
// };
console.log("토글버튼", action);
state.todos.map((item, i, arr) => {
if (item.id === action.id) {
console.log("토글버튼 isDone", arr[i].isDone);
// item.isDone ? (item.isDone = false) : (item.isDone = true);
arr[i].isDone ? (arr[i].isDone = false) : (arr[i].isDone = true);
console.log("토글버튼2 isDone", arr[i].isDone);
}
});
return { ...state, todos: [...state.todos] };
case GET_TODO_BY_ID:
return {
...state,
todo: state.todos.find((todo) => {
return todo.id === action.payload;
}),
};
default:
return state;
}
};
export default todos;
//configStore.js
import { createStore } from "redux";
import { combineReducers } from "redux";
import todos from "../modules/todos.js";
const rootReducer = combineReducers({
todos,
});
const store = createStore(rootReducer);
export default store;
//Home.js
import React from "react";
import Header from "../components/ui/Header";
import Layout from "../components/ui/Layout";
import Form from "../features/todos/components/Form";
import List from "../features/todos/components/List";
const Home = () => {
return (
<Layout>
<Header />
<Form />
<List />
</Layout>
);
};
export default Home;
//Detail.js
import React, { useEffect } from "react";
import styled from "styled-components";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate, useParams } from "react-router-dom";
import { getTodoByID } from "../redux/modules/todos.js";
const Detail = () => {
const dispatch = useDispatch();
const todo = useSelector((state) => state.todos.todo);
const { id } = useParams();
const navigate = useNavigate();
useEffect(() => {
dispatch(getTodoByID(Number(id)));
}, [dispatch, id]);
return (
<StContainer>
<StDialog>
<div>
<StDialogHeader>
<div>ID :{todo.id}</div>
<StButton
borderColor='#ddd'
onClick={() => {
navigate("/");
}}
>
이전으로
</StButton>
</StDialogHeader>
<StTitle>{todo.title}</StTitle>
<StBody>{todo.body}</StBody>
</div>
</StDialog>
</StContainer>
);
};
export default Detail;
const StContainer = styled.div`
border: 2px solid #eee;
width: 100%;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
`;
const StDialog = styled.div`
width: 600px;
height: 400px;
border: 1px solid #eee;
display: flex;
flex-direction: column;
justify-content: space-between;
`;
const StDialogHeader = styled.div`
display: flex;
height: 80px;
justify-content: space-between;
padding: 0 24px;
align-items: center;
`;
const StTitle = styled.h1`
padding: 0 24px;
`;
const StBody = styled.main`
padding: 0 24px;
`;
const StButton = styled.button`
border: 1px solid ${({ borderColor }) => borderColor};
height: 40px;
width: 120px;
background-color: #fff;
border-radius: 12px;
cursor: pointer;
`;
//List.js
import React from "react";
import styled from "styled-components";
import { useDispatch, useSelector } from "react-redux";
import { deleteTodo, toggleStatusTodo } from "../../../redux/modules/todos.js";
import { Link } from "react-router-dom";
const List = () => {
const dispatch = useDispatch();
const todos = useSelector((state) => state.todos.todos);
const onDeleteTodo = (id) => {
// console.log("잘찍힘");
dispatch(deleteTodo(id));
};
const onToggleStatusTodo = (id) => {
dispatch(toggleStatusTodo(id));
};
return (
<StListContainer>
<h2>Working.. 🔥</h2>
<StListWrapper>
{todos.map((todo) => {
if (!todo.isDone) {
return (
<StTodoContainer key={todo.id}>
<StLink to={`/${todo.id}`} key={todo.id}>
<div>상세보기</div>
</StLink>
<div>
<h2 className='todo-title'>{todo.title}</h2>
<div>{todo.body}</div>
</div>
<StDialogFooter>
<StButton
borderColor='red'
onClick={() => onDeleteTodo(todo.id)}
>
삭제하기
</StButton>
<StButton
borderColor='green'
onClick={() => onToggleStatusTodo(todo.id)}
>
{todo.isDone ? "취소!" : "완료!"}
</StButton>
</StDialogFooter>
</StTodoContainer>
);
} else {
return null;
}
})}
</StListWrapper>
<h2 className='list-title'>Done..! 🎉</h2>
<StListWrapper>
{todos.map((todo, index) => {
if (todo.isDone) {
return (
<StTodoContainer key={todo.id}>
<StLink to={`/${index}`} key={todo.id}>
<div>상세보기</div>
</StLink>
<div>
<h2 className='todo-title'>{todo.title}</h2>
<div>{todo.body}</div>
</div>
<StDialogFooter>
<StButton
borderColor='red'
onClick={() => onDeleteTodo(todo.id)}
>
삭제하기
</StButton>
<StButton
borderColor='green'
onClick={() => onToggleStatusTodo(todo.id)}
>
{todo.isDone ? "취소!" : "완료!"}
</StButton>
</StDialogFooter>
</StTodoContainer>
);
} else {
return null;
}
})}
</StListWrapper>
</StListContainer>
);
};
export default List;
const StListContainer = styled.div`
padding: 0 24px;
`;
const StListWrapper = styled.div`
display: flex;
flex-wrap: wrap;
gap: 12px;
`;
const StTodoContainer = styled.div`
width: 270px;
border: 4px solid teal;
min-height: 150px;
border-radius: 12px;
padding: 12px 24px 24px 24px;
`;
const StLink = styled(Link)`
text-decoration: none;
`;
const StDialogFooter = styled.footer`
display: flex;
justify-content: end;
padding: 12px;
gap: 12px;
`;
const StButton = styled.button`
border: 1px solid ${({ borderColor }) => borderColor};
height: 40px;
width: 120px;
background-color: #fff;
border-radius: 12px;
cursor: pointer;
`;
//Form.jsx
import React, { useState } from "react";
import styled from "styled-components";
import { useDispatch } from "react-redux";
import nextId from "react-id-generator";
import { addTodo } from "../../../redux/modules/todos.js";
const Form = () => {
const id = nextId();
const dispatch = useDispatch();
const [todo, setTodo] = useState({
// id: 0,
// title: "",
// body: "",
// isDone: false,
});
const onChangeHandler = (event) => {
const { name, value } = event.target;
setTodo({
...todo,
id: Date.now(),
isDone: false,
[name]: value,
// [e.target.name]: e.target.value,
});
// const { name, value } = event.target;
// setTodo({ ...todo, [name]: value });
};
const onSubmitHandler = (event) => {
event.preventDefault();
if (todo.title.trim() === "" || todo.body.trim() === "") return;
setTodo({
id: 0,
title: "",
body: "",
isDone: false,
});
dispatch(addTodo(todo));
// submit후 빈칸 유지를 위해 공객체 생성
setTodo({
id: "",
title: "",
content: "",
isDone: false,
});
//디스패치 하지 아니함
};
return (
<StAddForm onSubmit={onSubmitHandler}>
<StInputGroup>
<StFormLabel>제목</StFormLabel>
<StAddInput
type='text'
name='title'
value={todo.title}
onChange={onChangeHandler}
/>
<StFormLabel>내용</StFormLabel>
<StAddInput
type='text'
name='body'
value={todo.body}
onChange={onChangeHandler}
/>
</StInputGroup>
<StAddButton>추가하기</StAddButton>
</StAddForm>
);
};
export default Form;
const StInputGroup = styled.div`
display: flex;
align-items: center;
gap: 20px;
`;
const StFormLabel = styled.label`
font-size: 16px;
font-weight: 700;
`;
const StAddForm = styled.form`
background-color: #eee;
border-radius: 12px;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
padding: 30px;
gap: 20px;
`;
const StAddInput = styled.input`
height: 40px;
width: 240px;
border: none;
border-radius: 12px;
padding: 0 12px;
`;
const StAddButton = styled.button`
border: none;
height: 40px;
cursor: pointer;
border-radius: 10px;
background-color: teal;
width: 140px;
color: #fff;
font-weight: 700;
`;
//Layout.js
import React from "react";
import styled from "styled-components";
const Layout = ({ children }) => {
return <StLayout>{children}</StLayout>;
};
export default Layout;
const StLayout = styled.div`
max-width: 1200px;
min-width: 800px;
margin: 0 auto;
`;
//Header.js
import React from "react";
import styled from "styled-components";
const Header = () => {
return (
<StContainer>
<div>My Todo List</div>
<div>React</div>
</StContainer>
);
};
export default Header;
const StContainer = styled.div`
border: 1px solid #ddd;
height: 60px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
margin-bottom: 24px;
`;