juni
SingleProject_가계부(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;
'프로젝트 > 미니 프로젝트' 카테고리의 다른 글
SingleProject_리액트 가계부 redux (0) | 2024.05.28 |
---|---|
SingleProject_리액트 가계부 context (0) | 2024.05.26 |
React 학습 (0) | 2024.05.21 |
SingleProject_React 학습(TodoList) (0) | 2024.05.17 |
SingleProject_React 학습 (리랜더링 및 TodoList 완성) (0) | 2024.05.16 |