HyeonJun Jeon

[Implement] Header component, Calendar grid

...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
13 "@testing-library/user-event": "^13.5.0", 13 "@testing-library/user-event": "^13.5.0",
14 "react": "^18.1.0", 14 "react": "^18.1.0",
15 "react-dom": "^18.1.0", 15 "react-dom": "^18.1.0",
16 + "react-router-dom": "^6.3.0",
16 "react-scripts": "5.0.1", 17 "react-scripts": "5.0.1",
17 "web-vitals": "^2.1.4" 18 "web-vitals": "^2.1.4"
18 } 19 }
...@@ -8061,6 +8062,14 @@ ...@@ -8061,6 +8062,14 @@
8061 "he": "bin/he" 8062 "he": "bin/he"
8062 } 8063 }
8063 }, 8064 },
8065 + "node_modules/history": {
8066 + "version": "5.3.0",
8067 + "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz",
8068 + "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==",
8069 + "dependencies": {
8070 + "@babel/runtime": "^7.7.6"
8071 + }
8072 + },
8064 "node_modules/hoopy": { 8073 "node_modules/hoopy": {
8065 "version": "0.1.4", 8074 "version": "0.1.4",
8066 "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", 8075 "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
...@@ -13462,6 +13471,30 @@ ...@@ -13462,6 +13471,30 @@
13462 "node": ">=0.10.0" 13471 "node": ">=0.10.0"
13463 } 13472 }
13464 }, 13473 },
13474 + "node_modules/react-router": {
13475 + "version": "6.3.0",
13476 + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz",
13477 + "integrity": "sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==",
13478 + "dependencies": {
13479 + "history": "^5.2.0"
13480 + },
13481 + "peerDependencies": {
13482 + "react": ">=16.8"
13483 + }
13484 + },
13485 + "node_modules/react-router-dom": {
13486 + "version": "6.3.0",
13487 + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.3.0.tgz",
13488 + "integrity": "sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==",
13489 + "dependencies": {
13490 + "history": "^5.2.0",
13491 + "react-router": "6.3.0"
13492 + },
13493 + "peerDependencies": {
13494 + "react": ">=16.8",
13495 + "react-dom": ">=16.8"
13496 + }
13497 + },
13465 "node_modules/react-scripts": { 13498 "node_modules/react-scripts": {
13466 "version": "5.0.1", 13499 "version": "5.0.1",
13467 "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", 13500 "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
...@@ -21987,6 +22020,14 @@ ...@@ -21987,6 +22020,14 @@
21987 "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 22020 "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
21988 "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" 22021 "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
21989 }, 22022 },
22023 + "history": {
22024 + "version": "5.3.0",
22025 + "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz",
22026 + "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==",
22027 + "requires": {
22028 + "@babel/runtime": "^7.7.6"
22029 + }
22030 + },
21990 "hoopy": { 22031 "hoopy": {
21991 "version": "0.1.4", 22032 "version": "0.1.4",
21992 "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", 22033 "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
...@@ -25751,6 +25792,23 @@ ...@@ -25751,6 +25792,23 @@
25751 "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", 25792 "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
25752 "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==" 25793 "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A=="
25753 }, 25794 },
25795 + "react-router": {
25796 + "version": "6.3.0",
25797 + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz",
25798 + "integrity": "sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==",
25799 + "requires": {
25800 + "history": "^5.2.0"
25801 + }
25802 + },
25803 + "react-router-dom": {
25804 + "version": "6.3.0",
25805 + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.3.0.tgz",
25806 + "integrity": "sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==",
25807 + "requires": {
25808 + "history": "^5.2.0",
25809 + "react-router": "6.3.0"
25810 + }
25811 + },
25754 "react-scripts": { 25812 "react-scripts": {
25755 "version": "5.0.1", 25813 "version": "5.0.1",
25756 "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", 25814 "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
8 "@testing-library/user-event": "^13.5.0", 8 "@testing-library/user-event": "^13.5.0",
9 "react": "^18.1.0", 9 "react": "^18.1.0",
10 "react-dom": "^18.1.0", 10 "react-dom": "^18.1.0",
11 + "react-router-dom": "^6.3.0",
11 "react-scripts": "5.0.1", 12 "react-scripts": "5.0.1",
12 "web-vitals": "^2.1.4" 13 "web-vitals": "^2.1.4"
13 }, 14 },
......
...@@ -29,15 +29,5 @@ ...@@ -29,15 +29,5 @@
29 <body> 29 <body>
30 <noscript>You need to enable JavaScript to run this app.</noscript> 30 <noscript>You need to enable JavaScript to run this app.</noscript>
31 <div id="root"></div> 31 <div id="root"></div>
32 - <!--
33 - This HTML file is a template.
34 - If you open it directly in the browser, you will see an empty page.
35 -
36 - You can add webfonts, meta tags, or analytics to this file.
37 - The build step will place the bundled scripts into the <body> tag.
38 -
39 - To begin the development, run `npm start` or `yarn start`.
40 - To create a production bundle, use `npm run build` or `yarn build`.
41 - -->
42 </body> 32 </body>
43 </html> 33 </html>
......
1 import "./styles/App.css"; 1 import "./styles/App.css";
2 +import { BrowserRouter, Route, Routes } from "react-router-dom";
3 +
4 +import Calendar from "./pages/Calendar";
5 +import Home from "./pages/Home";
2 6
3 function App() { 7 function App() {
4 - return <div className="App"></div>; 8 + return (
9 + <BrowserRouter>
10 + <div className="App">
11 + <Routes>
12 + <Route path="/calendar/*" element={<Calendar />} />
13 + <Route exact path="/login" element={<></>} />
14 + <Route exact path="/setting" element={<></>} />
15 + <Route path="*" element={<Home />} />
16 + </Routes>
17 + </div>
18 + </BrowserRouter>
19 + );
5 } 20 }
6 21
7 export default App; 22 export default App;
......
1 +import "../styles/Grid.css";
2 +import GridHead from "./GridHead.js";
3 +import GridRow from "./GridRow.js";
4 +
5 +import React from "react";
6 +
7 +const Grid = () => {
8 + const renderRows = () => {
9 + const rows = [];
10 + for (let i = 0; i < 5; i++) {
11 + rows.push(<GridRow key={i} />);
12 + }
13 + return rows;
14 + };
15 +
16 + return (
17 + <div className="Grid">
18 + <GridHead />
19 + {renderRows()}
20 + </div>
21 + );
22 +};
23 +
24 +export default Grid;
1 +const GridHead = () => {
2 + const days = ["일", "월", "화", "수", "목", "금", "토"];
3 + const renderItems = () => {
4 + const items = [];
5 + for (let i = 0; i < 7; i++) {
6 + items.push(
7 + <div className="GridHeadItem" key={i}>
8 + {days[i]}
9 + </div>
10 + );
11 + }
12 + return items;
13 + };
14 +
15 + return <div className="GridHead">{renderItems()}</div>;
16 +};
17 +
18 +export default GridHead;
1 +const GridItem = () => {
2 + return <div className="GridItem"></div>;
3 +};
4 +
5 +export default GridItem;
1 +import GridItem from "./GridItem";
2 +
3 +const GridRow = () => {
4 + const renderItems = () => {
5 + const items = [];
6 + for (let i = 0; i < 7; i++) {
7 + items.push(<GridItem key={i} />);
8 + }
9 + return items;
10 + };
11 +
12 + return <div className="GridRow">{renderItems()}</div>;
13 +};
14 +
15 +export default GridRow;
1 +import { useContext } from "react";
2 +import { CalendarStateContext } from "../pages/Calendar";
3 +import "../styles/Header.css";
4 +
5 +const Header = () => {
6 + const [state, setState] = useContext(CalendarStateContext);
7 +
8 + const handleChangeState = (e) => {
9 + setState({
10 + ...state,
11 + [e.target.name]: e.target.value,
12 + });
13 + };
14 +
15 + const gotoToday = () => {
16 + const scope = state.scope;
17 + const today = new Date();
18 + const year = today.getFullYear();
19 + const month = today.getMonth() + 1;
20 + const date = today.getDate();
21 + setState({ scope, year, month, date });
22 + };
23 +
24 + const move = (e) => {
25 + const scope = state.scope;
26 + const current = new Date(state.year, state.month - 1, state.date);
27 + switch (scope) {
28 + case "month":
29 + current.setMonth(current.getMonth() + Number(e.target.value));
30 + break;
31 + case "week":
32 + current.setDate(current.getDate() + Number(e.target.value) * 7);
33 + break;
34 + case "day":
35 + current.setDate(current.getDate() + Number(e.target.value));
36 + break;
37 + default:
38 + }
39 + const year = current.getFullYear();
40 + const month = current.getMonth() + 1;
41 + const date = current.getDate();
42 + setState({ scope, year, month, date });
43 + };
44 +
45 + let headLabel;
46 + switch (state.scope) {
47 + case "month":
48 + case "week":
49 + headLabel = state.year + "년 " + state.month + "월";
50 + break;
51 + case "day":
52 + headLabel = state.year + "년 " + state.month + "월 " + state.date + "일";
53 + break;
54 + default:
55 + headLabel = "unexpected scope";
56 + }
57 +
58 + return (
59 + <header>
60 + <div className="hl">
61 + <span className="hls">확장 캘린더</span>
62 + </div>
63 + <div className="hc">
64 + <button className="hcb" onClick={gotoToday}>
65 + 오늘
66 + </button>
67 + <div className="hcd">
68 + <button onClick={move} value={-1}>
69 + {"ᐸ"}
70 + </button>
71 + <button onClick={move} value={+1}>
72 + {"ᐳ"}
73 + </button>
74 + </div>
75 + <span className="hcs">{headLabel}</span>
76 + </div>
77 + <div className="hr">
78 + <div className="hrd">
79 + <button
80 + disabled={state.scope === "day"}
81 + onClick={handleChangeState}
82 + name="scope"
83 + value="day"
84 + >
85 +
86 + </button>
87 + <button
88 + disabled={state.scope === "week"}
89 + onClick={handleChangeState}
90 + name="scope"
91 + value="week"
92 + >
93 +
94 + </button>
95 + <button
96 + disabled={state.scope === "month"}
97 + onClick={handleChangeState}
98 + name="scope"
99 + value="month"
100 + >
101 +
102 + </button>
103 + </div>
104 + </div>
105 + </header>
106 + );
107 +};
108 +
109 +export default Header;
File mode changed
1 +import React, { useState } from "react";
2 +import { Route, Routes } from "react-router-dom";
3 +import Grid from "../components/Grid";
4 +import Header from "../components/Header";
5 +import "../styles/Home.css";
6 +
7 +export const CalendarStateContext = React.createContext();
8 +
9 +const Calendar = () => {
10 + const today = new Date();
11 + const year = today.getFullYear();
12 + const month = today.getMonth() + 1;
13 + const date = today.getDate();
14 + //const day = today.getDay();
15 +
16 + //scope는 day, state는 date
17 + const [state, setState] = useState({
18 + scope: "month",
19 + year: year,
20 + month: month,
21 + date: date,
22 + });
23 +
24 + return (
25 + <CalendarStateContext.Provider value={[state, setState]}>
26 + <div className="Calendar">
27 + <Header />
28 + <Routes>
29 + <Route path="/month/*" element={<Grid />} />
30 + <Route path="/week/*" element={<></>} />
31 + <Route path="/day/*" element={<></>} />
32 + </Routes>
33 + </div>
34 + </CalendarStateContext.Provider>
35 + );
36 +};
37 +
38 +export default Calendar;
1 +import { useEffect } from "react";
2 +import { useNavigate } from "react-router-dom";
3 +
4 +const Home = () => {
5 + const navigate = useNavigate();
6 + useEffect(() => navigate("/calendar/month"), [navigate]);
7 +};
8 +
9 +export default Home;
1 +h1,
2 +h2 {
3 + margin: 0;
4 +}
......
1 +.GridItem,
2 +.GridHeadItem {
3 + border-style: solid;
4 + border-width: thin;
5 + height: 150px;
6 + flex-basis: 100px;
7 + flex-grow: 1;
8 +}
9 +
10 +.GridHeadItem {
11 + height: 30px;
12 + text-align: center;
13 + line-height: 30px;
14 + padding: 10px 5px 10px 5px;
15 +}
16 +
17 +.GridRow,
18 +.GridHead {
19 + display: flex;
20 +}
21 +
22 +.Grid {
23 + display: flex;
24 + flex-direction: column;
25 +}
1 +header {
2 + background-color: rgba(255, 255, 255, 1);
3 + display: flex;
4 + padding: 8px;
5 + justify-content: space-between;
6 + align-items: center;
7 +}
8 +
9 +.hl {
10 + padding-right: 50px;
11 +}
12 +
13 +.hls {
14 + padding-left: 8px;
15 + font-size: x-large;
16 +}
17 +
18 +.hc {
19 + height: 48px;
20 + align-items: center;
21 + display: flex;
22 + justify-content: left;
23 + flex-grow: 1;
24 +}
25 +
26 +.hcb {
27 + height: 34px;
28 + margin: 10px;
29 +}
30 +
31 +.hcd {
32 + margin: 10px;
33 +}
34 +
35 +.hcd > button {
36 + font-size: large;
37 +}
38 +
39 +.hcs {
40 + font-size: x-large;
41 + margin: 10px;
42 +}
43 +
44 +.hrd > button {
45 + padding: 8px 14px 8px 14px;
46 + font-size: medium;
47 +}
File mode changed