[TIL/React] 2023/05/16

원민관·2023년 5월 16일
0

[TIL]

목록 보기
70/159

src/commons/button.js

import { styled } from "styled-components";

export const CommonButton = styled.button`
  background-color: #20c997;
  opacity: 0.8;
  color: white;
  font-weight: bolder;
  border: none;
  border-radius: 7px;
  padding: 10px;
  &:hover {
    border: 3px solid gray;
  }
  cursor: pointer;
`;

export const CommonButtonWrapper = styled.div`
  display: flex;
  justify-content: center;
  column-gap: 5px;
  margin-bottom: 5px;
`;

src/commons/keyframe.js

import { keyframes } from "styled-components";

export const rotate = keyframes`
  from {
    transform: rotate(0deg);
  }

  to {
    transform: rotate(360deg);
  }
`;

src/components/InputTodo.js

import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import { styled } from "styled-components";
import { addTodo } from "../reducers/reducer";

const inputArray = ["title", "subtitle", "desc"];

// styled
const InputFieldWrapper = styled.div`
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-top: 30px;
`;
const TodoServiceTitle = styled.h1`
  text-align: center;
  color: #20c997;
  font-size: 45px;
  margin-top: 30px;
  font-weight: bolder;
`;
const InputBox = styled.input`
  margin-bottom: 10px;
  width: 250px;
  border: 3px solid gray;
  border-radius: 5px;
  padding: 4px;
`;
const AddButton = styled.button`
  background-color: #20c997;
  opacity: 0.8;
  color: white;
  font-weight: bolder;
  border: none;
  border-radius: 10px;
  padding: 10px;
  margin-top: 10px;
  &:hover {
    border: 3px solid gray;
  }
  cursor: pointer;
`;
const NavButton = styled.button`
  background-color: #20c997;
  opacity: 0.8;
  color: white;
  font-weight: bolder;
  border: none;
  border-radius: 10px;
  padding: 8px;
  margin-top: 0px;
  &:hover {
    border: 3px solid gray;
  }
  cursor: pointer;
`;

const InputTodo = () => {
  const todo = useSelector((state) => state?.todo);
  const dispatch = useDispatch();
  const navigate = useNavigate();

  const [inputValue, setInputValue] = useState({
    title: "",
    subtitle: "",
    desc: "",
    isDone: false,
    editMode: false,
  });

  const handelInputValue = (e) => {
    const { value, name } = e?.target;
    setInputValue((prev) => {
      return { ...prev, [name]: value };
    });
  };

  const handleAddClick = () => {
    dispatch(
      addTodo({
        ...inputValue,
        id: `${inputValue?.title}${inputValue?.subtitle}${inputValue?.desc}`,
      })
    );
    setInputValue({
      title: "",
      subtitle: "",
      desc: "",
      isDone: false,
      editMode: false,
    });
  };

  console.log({ inputValue, todo });
  return (
    <>
      <NavButton onClick={() => navigate("/")}>Main Page</NavButton>
      <InputFieldWrapper>
        <TodoServiceTitle>TodoList</TodoServiceTitle>
        {inputArray?.map((elem, idx) => {
          return (
            <div key={idx}>
              <InputBox
                name={elem}
                value={inputValue?.[elem]}
                onChange={handelInputValue}
                placeholder={`Add your ${elem}!`}
              />
            </div>
          );
        })}
        <AddButton onClick={handleAddClick}>Add your Todo!</AddButton>
      </InputFieldWrapper>
    </>
  );
};

export default InputTodo;

src/components/MainPageTemplate.js

import React from "react";
import styled from "styled-components";
import { rotate } from "../commons/keyframe";
import { useNavigate } from "react-router-dom";

const MainTemplateBlock = styled.div`
  width: 512px;
  height: 900px;

  background: white;
  background: linear-gradient(to bottom, white, ivory);
  border-radius: 16px;
  box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.04);

  margin: 0 auto;
  margin-top: 96px;
  margin-bottom: 32px;
  display: flex;
  flex-direction: column;
`;

const MainTitle = styled.h1`
  text-align: center;
  color: #20c997;
  font-size: 60px;
  margin-top: 40px;
  font-weight: bolder;
`;
const MainTextWrapper = styled.div`
  display: flex;
  flex-direction: column;
  align-items: center;
`;
const MainText = styled.p`
  margin: 50px;
  font-weight: bolder;
  color: gray;
  font-size: 40px;
`;
const MainBtnWrapper = styled.div`
  display: flex;
  justify-content: center;
  column-gap: 30px;
  margin-top: 90px;
`;
const MainBtn = styled.button`
  background-color: #20c997;
  opacity: 0.8;
  color: white;
  font-weight: bolder;
  border: none;
  border-radius: 10px;
  padding: 10px;
  &:hover {
    border: 3px solid gray;
  }
  cursor: pointer;
`;
const Rotate = styled.div`
  display: inline-block;
  animation: ${rotate} 2s linear infinite;
  padding: 2rem 1rem;
  font-size: 40px;
`;

const MainPageTemplate = () => {
  const navigate = useNavigate();
  return (
    <MainTemplateBlock>
      <MainTitle>MYL!</MainTitle>

      <MainTextWrapper>
        <MainText>Manage</MainText>
        <MainText>Your</MainText>
        <MainText>Life!</MainText>
        <Rotate>🔥</Rotate>
      </MainTextWrapper>

      <MainBtnWrapper>
        <MainBtn onClick={() => navigate("/todolist")}>
          TodoList Service
        </MainBtn>
        <MainBtn onClick={() => navigate("/phonebook")}>
          PhoneBook Service
        </MainBtn>
      </MainBtnWrapper>
    </MainTemplateBlock>
  );
};

export default MainPageTemplate;

src/components/PhoneBookService.js

import React from "react";

const PhoneBookService = () => {
  return <div>연락처 서비스</div>;
};

export default PhoneBookService;

src/components/TodoDetail.js

import React from "react";
import { useParams } from "react-router-dom";

const TodoDetail = () => {
  const { id } = useParams();
  return <div>{id}에 대한 투두 상세 페이지 </div>;
};

export default TodoDetail;

src/components/TodoSection.js

import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { styled } from "styled-components";
import {
  cancelTodo,
  completeTodo,
  deleteTodo,
  editTodo,
  saveTodo,
} from "../reducers/reducer";
import { CommonButton } from "../commons/button";
import { CommonButtonWrapper } from "../commons/button";
import { useNavigate } from "react-router-dom";
// style
const TodoTitle = styled.h2`
  text-align: center;
  color: #20c997;
  font-size: 30px;
  margin-top: 30px;
  font-weight: bolder;
`;

const CompleteTitle = styled.h2`
  text-align: center;
  color: #20c997;
  font-size: 30px;
  margin-top: 30px;
  font-weight: bolder;
`;

const TodoCard = styled.div`
  border: 3px solid gray;
  border-radius: 10px;
  margin-bottom: 10px;
  margin: 20px;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 10px;
`;

const EditBox = styled.input`
  margin-bottom: 10px;
  width: 250px;
  border: 3px solid gray;
  border-radius: 5px;
  padding: 4px;
`;

const TodoSection = () => {
  const navigate = useNavigate();
  const todos = useSelector((state) => state?.todo?.todo);
  const dispatch = useDispatch();
  const noCompleteArray = todos?.filter((elem) => elem?.isDone === false);
  const completeArray = todos?.filter((elem) => elem?.isDone === true);

  const [editValue, setEditValue] = useState({});

  const handleEditValue = (event) => {
    const { value, name } = event?.target;
    setEditValue((prev) => {
      return { ...prev, [name]: value };
    });
  };
  const handleDeleteClick = (id) => {
    dispatch(deleteTodo(id));
  };
  const handleCompleteClick = (id) => {
    dispatch(completeTodo(id));
  };
  const handleEditClick = (id) => {
    dispatch(editTodo(id));
    let findValue = [...noCompleteArray]?.find((todo) => todo?.id === id);
    setEditValue(findValue);
  };
  const handleCancelClick = (id) => {
    dispatch(cancelTodo(id));
  };
  const handleSaveClick = (id) => {
    dispatch(saveTodo({ id: id, updatedTodo: editValue }));
  };

  console.log({ todos, editValue });
  return (
    <div>
      <TodoTitle>Your Todo 🔥</TodoTitle>
      {noCompleteArray.map((todo) => {
        return todo?.editMode ? (
          <TodoCard key={todo?.id}>
            <EditBox
              name="title"
              value={editValue?.title}
              onChange={handleEditValue}
              placeholder={`Edit your title!`}
            />
            <EditBox
              name="subtitle"
              value={editValue?.subtitle}
              onChange={handleEditValue}
              placeholder={`Edit your subtitle!`}
            />
            <EditBox
              name="desc"
              value={editValue?.desc}
              onChange={handleEditValue}
              placeholder={`Edit your desc!`}
            />

            <CommonButtonWrapper>
              <CommonButton onClick={() => handleSaveClick(todo?.id)}>
                Save
              </CommonButton>
              <CommonButton onClick={() => handleCancelClick(todo?.id)}>
                Cancel
              </CommonButton>
            </CommonButtonWrapper>
          </TodoCard>
        ) : (
          <TodoCard key={todo?.id}>
            <div onClick={() => navigate(`/tododetail/${todo?.id}`)}>
              <p>{todo?.title}</p>
              <p>{todo?.subtitle}</p>
              <p>{todo?.desc}</p>
            </div>
            <CommonButtonWrapper>
              <CommonButton onClick={() => handleEditClick(todo?.id)}>
                Edit
              </CommonButton>
              <CommonButton onClick={() => handleCompleteClick(todo?.id)}>
                Complete
              </CommonButton>
              <CommonButton onClick={() => handleDeleteClick(todo?.id)}>
                Delete
              </CommonButton>
            </CommonButtonWrapper>
          </TodoCard>
        );
      })}
      <div>
        <CompleteTitle>Complete 👏</CompleteTitle>

        {completeArray.map((todo) => {
          return (
            <TodoCard key={todo?.id}>
              <div>
                <p>{todo?.title}</p>
                <p>{todo?.subtitle}</p>
                <p>{todo?.desc}</p>
              </div>
              <CommonButtonWrapper>
                <CommonButton onClick={() => handleDeleteClick(todo?.id)}>
                  Delete
                </CommonButton>
              </CommonButtonWrapper>
            </TodoCard>
          );
        })}
      </div>
    </div>
  );
};

export default TodoSection;

src/components/TodoService.js

import React from "react";
import { styled } from "styled-components";
import InputTodo from "./InputTodo";
import TodoSection from "./TodoSection";

const TodoSeviceTemplate = styled.div`
  width: 512px;
  height: 900px;

  background: white;
  background: linear-gradient(to bottom, white, ivory);
  border-radius: 16px;
  box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.04);

  margin: 0 auto;
  margin-top: 96px;
  margin-bottom: 32px;
  display: flex;
  flex-direction: column;

  overflow-y: auto;
`;

const TodoService = () => {
  return (
    <TodoSeviceTemplate>
      <InputTodo />
      <TodoSection />
    </TodoSeviceTemplate>
  );
};

export default TodoService;

src/reducers/reducer.js

import { createSlice } from "@reduxjs/toolkit";

const todoSlice = createSlice({
  name: "todo",
  initialState: {
    todo: [],
  },
  reducers: {
    addTodo: (state, action) => {
      state.todo.push(action.payload);
    },
    deleteTodo: (state, action) => {
      state.todo = state.todo.filter((todo) => todo.id !== action.payload);
    },
    completeTodo: (state, action) => {
      state.todo = state.todo.map((todo) =>
        todo.id === action.payload ? { ...todo, isDone: true } : todo
      );
    },
    editTodo: (state, action) => {
      state.todo = state.todo.map((todo) =>
        todo.id === action.payload ? { ...todo, editMode: true } : todo
      );
    },
    saveTodo: (state, action) => {
      state.todo = state.todo.map((todo) =>
        todo.id === action.payload.id
          ? { ...action.payload.updatedTodo, editMode: false }
          : todo
      );
    },
    cancelTodo: (state, action) => {
      state.todo = state.todo.map((todo) =>
        todo.id === action.payload ? { ...todo, editMode: false } : todo
      );
    },
  },
});

export const {
  addTodo,
  deleteTodo,
  completeTodo,
  editTodo,
  saveTodo,
  cancelTodo,
} = todoSlice.actions;

export default todoSlice.reducer;

src/reducers/store.js

import { configureStore } from "@reduxjs/toolkit";
import reducer from "./reducer";

const store = configureStore({
  reducer: {
    todo: reducer,
  },
});

export default store;

src/App.js

import React from "react";
import { Route, Routes } from "react-router-dom";
import { createGlobalStyle } from "styled-components";
import MainPageTemplate from "./components/MainPageTemplate";
import TodoService from "./components/TodoService";
import PhoneBookService from "./components/PhoneBookService";
import TodoDetail from "./components/TodoDetail";

const GlobalStyle = createGlobalStyle`
body {
  background: #e9ecef;
}`;

function App() {
  return (
    <>
      <GlobalStyle />
      <Routes>
        <Route path="/" element={<MainPageTemplate />} />
        <Route path="/todolist" element={<TodoService />} />
        <Route path="/phonebook" element={<PhoneBookService />} />
        <Route path="/tododetail/:id" element={<TodoDetail />} />
      </Routes>
    </>
  );
}

export default App;

src/index.js

import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { BrowserRouter } from "react-router-dom";
import { Provider } from "react-redux";
import store from "./reducers/store";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <BrowserRouter>
    <Provider store={store}>
      <App />
    </Provider>
  </BrowserRouter>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

중간 결산

profile
Write a little every day, without hope, without despair ✍️

0개의 댓글