본문 바로가기

카테고리 없음

Study_240524 ( React ) 개인프로젝트_가계부

 

import { BrowserRouter, Route, Routes } from "react-router-dom";
import Home from "./pages/Home";
import Detail from "./pages/Detail";
import GlobalStyle from "./components/GlobalStyle.jsx";

function App() {
  return (
    <BrowserRouter>
      <GlobalStyle />
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/detail/:id" element={<Detail />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;

 

import { useState, useEffect } from "react";
import BoxContainer from "../components/BoxContainer";
import AccountBookForm from "../components/AccountBookForm";

const Home = () => {
  const initMonthData = [];

  for (let i = 1; i <= 12; i++) {
    initMonthData.push({ id: i, month: `${i}월`, texts: [] });
  }

  const [monthData, setMonthData] = useState(initMonthData);
  const [selectedMonth, setSelectedMonth] = useState(
    localStorage.getItem("selectedMonth")
      ? Number(localStorage.getItem("selectedMonth"))
      : 1
  );

  useEffect(() => {
    const storedMonthData =
      JSON.parse(localStorage.getItem("monthData")) || initMonthData;
    const storedSelectedMonth =
      JSON.parse(localStorage.getItem("selectedMonth")) || 1;

    setMonthData(storedMonthData);
    setSelectedMonth(storedSelectedMonth);
  }, []);

  useEffect(() => {
    if (monthData !== initMonthData) {
      localStorage.setItem("monthData", JSON.stringify(monthData));
    }
  }, [monthData]);

  useEffect(() => {
    localStorage.setItem("selectedMonth", JSON.stringify(selectedMonth));
  }, [selectedMonth]);

  return (
    <div>
      <h1>Home</h1>
      <AccountBookForm
        setMonthData={setMonthData}
        selectedMonth={selectedMonth}
      />
      <BoxContainer
        monthData={monthData}
        selectedMonth={selectedMonth}
        setSelectedMonth={setSelectedMonth}
      />
    </div>
  );
};

export default Home;

 

import { useState, useEffect } from "react";
import { v4 as uuidv4 } from "uuid";

const AccountBookForm = ({ setMonthData, selectedMonth }) => {
  const onAdd = (text) => {
    setMonthData((prevData) =>
      prevData.map((month) =>
        month.id === selectedMonth
          ? { ...month, texts: [...month.texts, text] }
          : month
      )
    );
  };

  const getDefaultDate = (month) => {
    const year = new Date().getFullYear();
    const monthString = month < 10 ? `0${month}` : month;
    return `${year}-${monthString}-01`;
  };

  const [formData, setFormData] = useState({
    date: getDefaultDate(selectedMonth),
    item: "",
    amount: "",
    description: "",
  });

  useEffect(() => {
    setFormData((prevData) => ({
      ...prevData,
      date: getDefaultDate(selectedMonth),
    }));
  }, [selectedMonth]);

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData({
      ...formData,
      [name]: value,
    });
  };

  const validateForm = () => {
    const { date, amount } = formData;
    const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
    const amountRegex = /^\d+(\.\d{1,2})?$/;

    if (!dateRegex.test(date)) {
      alert("날짜 형식이 올바르지 않습니다. YYYY-MM-DD 형식으로 입력해주세요.");
      return false;
    }

    if (!amountRegex.test(amount)) {
      alert("금액 형식이 올바르지 않습니다. 숫자만 입력해주세요.");
      return false;
    }

    return true;
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    if (validateForm()) {
      onAdd({ id: uuidv4(), ...formData });
      setFormData({
        date: getDefaultDate(selectedMonth),
        item: "",
        amount: "",
        description: "",
      });
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        name="date"
        value={formData.date}
        onChange={handleChange}
        placeholder="YYYY-MM-DD"
      />
      <input
        type="text"
        name="item"
        value={formData.item}
        onChange={handleChange}
        placeholder="지출 항목"
      />
      <input
        type="text"
        name="amount"
        value={formData.amount}
        onChange={handleChange}
        placeholder="지출 금액"
      />
      <input
        type="text"
        name="description"
        value={formData.description}
        onChange={handleChange}
        placeholder="지출 내용"
      />
      <button type="submit">저장</button>
    </form>
  );
};

export default AccountBookForm;

 

import styled from "styled-components";
import Box from "./Box";
import TextBox from "./TextBox";

const Container = styled.div`
  display: flex;
  flex-direction: column;
  align-items: center;
`;

const BoxesWrapper = styled.div`
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
`;

function BoxContainer({ monthData, selectedMonth, setSelectedMonth }) {
  const handleClick = (id) => {
    setSelectedMonth(id);
  };

  const selectedTexts =
    monthData.find((month) => month.id === selectedMonth)?.texts || [];

  return (
    <Container>
      <BoxesWrapper>
        {monthData.map((month) => (
          <Box
            key={month.id}
            id={month.id}
            month={month.month}
            onClick={handleClick}
            selectedMonth={selectedMonth}
          />
        ))}
      </BoxesWrapper>
      <TextBox texts={selectedTexts} />
    </Container>
  );
}

export default BoxContainer;

 

import styled from "styled-components";

const StyledBox = styled.div`
  width: 200px;
  background-color: ${(props) => (props.selected ? "blue" : "gray")};
  color: white;
  padding: 10px;
  margin: 10px;
  cursor: pointer;
  text-align: center;
  border-radius: 5px;
`;

function Box({ id, month, onClick, selectedMonth }) {
  const isSelected = selectedMonth === id;

  const handleClick = () => {
    onClick(id);
  };

  return (
    <StyledBox onClick={handleClick} selected={isSelected}>
      <h3>{month}</h3>
    </StyledBox>
  );
}

export default Box;

 

import { Link } from "react-router-dom";
import styled from "styled-components";

const StyledTextBox = styled.div`
  width: 100%;
  background-color: white;
  color: black;
  padding: 20px;
  margin-top: 20px;
  text-align: center;
  border-radius: 10px;

  li {
    background-color: lightgray;
    padding: 10px;
    border-radius: 10px;
    margin-bottom: 20px;
  }
`;

function TextBox({ texts }) {
  return (
    <StyledTextBox>
      {texts.length > 0 ? (
        <ul>
          {texts.map((text) => (
            <li key={text.id}>
              <Link to={`/detail/${text.id}`} state={{ text }}>
                {text.date} <br />
                {text.item} - {text.description} {text.amount}
              </Link>
            </li>
          ))}
        </ul>
      ) : (
        <p>텍스트를 입력해주세요.</p>
      )}
    </StyledTextBox>
  );
}

export default TextBox;

 

import { createGlobalStyle } from "styled-components";
import reset from "styled-reset";

const GlobalStyle = createGlobalStyle`
    ${reset}
    body {
        font-family: "Helvetica", "Arial", sans-serif;
        line-height: 1.5;
        font-size:30px;
    }
`;

export default GlobalStyle;

 

import { useRef, useEffect } from "react";
import { useParams, useNavigate, Link } from "react-router-dom";

function Detail() {
  const { id } = useParams();
  const navigate = useNavigate();

  const initialText = JSON.parse(sessionStorage.getItem("selectedText")) || {};
  const selectedMonth =
    JSON.parse(sessionStorage.getItem("selectedMonth")) || 1;

  const dateRef = useRef(initialText.date || "");
  const itemRef = useRef(initialText.item || "");
  const amountRef = useRef(initialText.amount || "");
  const descriptionRef = useRef(initialText.description || "");

  useEffect(() => {
    const monthData = JSON.parse(localStorage.getItem("monthData")) || [];
    let foundText = null;

    monthData.forEach((month) => {
      month.texts.forEach((text) => {
        if (text.id === id) {
          foundText = text;
        }
      });
    });

    if (foundText) {
      dateRef.current.value = foundText.date;
      itemRef.current.value = foundText.item;
      amountRef.current.value = foundText.amount;
      descriptionRef.current.value = foundText.description;
    }
  }, [id]);

  const handleSave = () => {
    const monthData = JSON.parse(localStorage.getItem("monthData")) || [];

    const updatedText = {
      id,
      date: dateRef.current.value,
      item: itemRef.current.value,
      amount: amountRef.current.value,
      description: descriptionRef.current.value,
    };

    const updatedMonthData = monthData.map((month) => {
      return {
        ...month,
        texts: month.texts.map((t) =>
          t.id === updatedText.id ? updatedText : t
        ),
      };
    });

    localStorage.setItem("monthData", JSON.stringify(updatedMonthData));
    navigate("/", { state: { selectedMonth } });
  };

  const handleDelete = () => {
    if (window.confirm("정말로 삭제하시겠습니까?")) {
      const monthData = JSON.parse(localStorage.getItem("monthData")) || [];

      const updatedMonthData = monthData.map((month) => {
        return {
          ...month,
          texts: month.texts.filter((t) => t.id !== id),
        };
      });

      localStorage.setItem("monthData", JSON.stringify(updatedMonthData));
      navigate("/", { state: { selectedMonth } });
    }
  };

  return (
    <div>
      <h1>Detail</h1>
      <div>
        <input type="text" name="date" ref={dateRef} />
        <br />
        <input type="text" name="item" ref={itemRef} />
        <br />
        <input type="text" name="amount" ref={amountRef} />
        <br />
        <input type="text" name="description" ref={descriptionRef} />
        <br />
      </div>
      <button onClick={handleSave}>수정</button>
      <button onClick={handleDelete}>삭제</button>
      <Link to="/" state={{ selectedMonth }}>
        뒤로 가기
      </Link>
    </div>
  );
}

export default Detail;