diff --git a/front/public/dummy/Exercise.json b/front/public/dummy/Exercise.json new file mode 100644 index 0000000000000000000000000000000000000000..9c682ec8b29cd097d377a0ca52e1662ddabaf957 --- /dev/null +++ b/front/public/dummy/Exercise.json @@ -0,0 +1,92 @@ +[ + { + "date": "2024-11-01", + "exercises": [ + { + "video_title": "workout1", + "video_tag": "head", + "video_time": "10:49" + }, + { + "video_title": "workout6", + "video_tag": "neck", + "video_time": "22:49" + } + ] + }, + { + "date": "2024-11-08", + "exercises": [ + { + "video_title": "workout2", + "video_tag": "neck", + "video_time": "7:55" + } + ] + }, + { + "date": "2024-11-11", + "exercises": [ + { + "video_title": "workout1", + "video_tag": "head", + "video_time": "10:49" + } + ] + }, + { + "date": "2024-11-27", + "exercises": [ + { + "video_title": "workout3", + "video_tag": "foot", + "video_time": "20:04" + } + ] + }, + { + "date": "2024-12-02", + "exercises": [ + { + "video_title": "workout3", + "video_tag": "foot", + "video_time": "20:04" + } + ] + }, + { + "date": "2024-12-07", + "exercises": [ + { + "video_title": "workout3", + "video_tag": "foot", + "video_time": "20:04" + } + ] + }, + { + "date": "2024-12-27", + "exercises": [ + { + "video_title": "workout3", + "video_tag": "foot", + "video_time": "20:04" + } + ] + }, + { + "date": "2024-12-31", + "exercises": [ + { + "video_title": "workout3", + "video_tag": "foot", + "video_time": "20:04" + } + ] + } + + + + + +] diff --git a/front/public/dummy/Goal.json b/front/public/dummy/Goal.json new file mode 100644 index 0000000000000000000000000000000000000000..4c312d89b5d69f956244228960b693d76bad923c --- /dev/null +++ b/front/public/dummy/Goal.json @@ -0,0 +1,6 @@ +{ + "goal_weekly": 3, + "goal_daily": [1, 1, 0, 0, 1, 0, 0], + "goal_daily_time": "30:00", + "goal_weight": 42 +} \ No newline at end of file diff --git a/front/public/dummy/User.json b/front/public/dummy/User.json new file mode 100644 index 0000000000000000000000000000000000000000..25214ebb2699553fefb7708df84f4561f1a82a93 --- /dev/null +++ b/front/public/dummy/User.json @@ -0,0 +1,7 @@ +{ + "user_id": "asdf", + "user_name": "김ㅇㅇ", + "user_gender": 0, + "user_age": 10, + "user_email": "asdf@adf.com" +} \ No newline at end of file diff --git a/front/src/App.jsx b/front/src/App.jsx index c4c6494be7b9caea69eda6c40cd7211114019b82..bc1d0f94d3c527fe635faf26b08834c4ced52f2b 100644 --- a/front/src/App.jsx +++ b/front/src/App.jsx @@ -4,6 +4,8 @@ import Header from './components/common/Header'; import Home from './pages/Home'; import Footer from './components/common/Footer' import Sign from './pages/Sign'; +import HabitTracker from './pages/HabitTracker'; +import Goal from './pages/Goal'; import MyPage from './pages/MyPage'; import Workout from './pages/Workout'; import Routine from './pages/Routine'; @@ -21,6 +23,8 @@ function App(){ <Routes> <Route exact path="/" element={<Home />} /> <Route path="/sign" element={<Sign />} /> + <Route path="/habitTracker" element={<HabitTracker />} /> + <Route path="/habitTracker/Goal" element={<Goal />} /> <Route path="/workout" element={<Workout />} /> <Route path="/routine" element={<Routine />} /> <Route path="/mypage" element={<MyPage />} /> diff --git a/front/src/api/HabitTrackerAPI.js b/front/src/api/HabitTrackerAPI.js new file mode 100644 index 0000000000000000000000000000000000000000..115058ea723b5b16eb27479c62b7a4c138546bb3 --- /dev/null +++ b/front/src/api/HabitTrackerAPI.js @@ -0,0 +1,114 @@ +async function getGoal(){ + try{ + const uri = `/api/habitTracker/goal` + const response = await fetch(uri, { + method: "GET", + headers: { + "Authorization": `Bearer ${localStorage.getItem('accessToken')}`, + "Content-Type": "application/json", + }, + }); + const data = await response.json(); + if (!data) + throw new Error('목표 조회 실패'); + else return data; + } catch(err){ + console.log(err.message); + } + // const uri = '/dummy/Goal.json' + // const response = await fetch(uri); + +}; + +async function addGoal(goalData){ + // const body = { + // goal_weekly: goalData.goal_weekly, + // goal_daily: goalData.goal_daily, + // goal_daily_time: goalData.goal_daily_time, + // goal_weight: goalData.goal_weight + // } + try{ + const uri = `/api/habitTracker/goal` + const response = await fetch(uri, { + method: "PUT", + headers: { + "Authorization": `Bearer ${localStorage.getItem('accessToken')}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(goalData) + }); + const responseData = await response.json(); + if (!response.ok) { + throw new Error(responseData.message || '목표를 설정하는데 실패했습니다.'); + } + else return responseData; + } catch(err){ + console.log(err.message); + } +}; + +// async function getUserData(){ +// try{ +// //const uri = `/api/user` +// //const response = await fetch(uri, { +// // method: "GET", +// // headers: { +// // //JWT +// // "Content-Type": "application/json", +// // }, +// // }); +// const uri = '/dummy/User.json' +// const response = await fetch(uri); + +// if (!response.ok){ +// throw new Error('Network error', response.status); +// } +// const data = await response.json(); +// return data; +// } catch(err) { +// console.log(err.message); +// } +// }; + + +async function getUserData(){ + try{ + const uri = `/api/user/profile` + const response = await fetch(uri, { + method: "GET", + headers: { + "Authorization": `Bearer ${localStorage.getItem('accessToken')}`, + "Content-Type": "application/json", + }, + }); + const data = await response.json(); + if(!data) throw new Error('회원정보 조회 실패'); + else return data; + } catch(err){ + console.log(err.message); + } +} + + +async function getMonthlyRecord(year, month) { + try{ + const uri = `/api/habitTracker/records?period=${year}-${month}`; + const response = await fetch(uri, { + method: "GET", + headers: { + "Authorization": `Bearer ${localStorage.getItem('accessToken')}`, + "Content-Type": "application/json", + }, + }); + // const uri = '/dummy/Exercise.json' + const data = await response.json(); + if(!data || !data.ok) + throw new Error(data.message || '기록을 불러오는데 실패했습니다.'); + else return data; + } catch(err){ + console.log(err.message); + } +} + + +export {getGoal, addGoal, getUserData, getMonthlyRecord}; \ No newline at end of file diff --git a/front/src/components/Footer.jsx b/front/src/components/Footer.jsx new file mode 100644 index 0000000000000000000000000000000000000000..2bcb9c7006da894541f426151cd1bbf0e3641ba3 --- /dev/null +++ b/front/src/components/Footer.jsx @@ -0,0 +1,31 @@ +import react from 'react'; +import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom'; + +import './index.css'; + +function Footer() { + return ( + <div id="footer"> + <ul> + <li> 웹시스템설계 </li> + <li> 9조 </li> + </ul> + <ul> + <li> 백엔드 </li> + <li> 문경호 </li> + <li> 김다인 </li> + </ul> + <ul> + <li> 프론트엔드 </li> + <li> 박태현 </li> + <li> 장지윤 </li> + </ul> + <ul> + <li>DOCUMENT</li> + <li><a href='https://git.ajou.ac.kr/wss9/fiturring' className="link" >GitLab</a></li> + <li><a href='https://www.notion.so/2024-2-130669572a77805db7e8e4f991ad455e?pvs=4' className="link">Notion</a></li> + </ul> + </div> + ) +} +export default Footer; \ No newline at end of file diff --git a/front/src/components/HabitTracker/Calendar.jsx b/front/src/components/HabitTracker/Calendar.jsx new file mode 100644 index 0000000000000000000000000000000000000000..ccc8cf6a89a80262f0d214cde9f1c962c2ea81e8 --- /dev/null +++ b/front/src/components/HabitTracker/Calendar.jsx @@ -0,0 +1,89 @@ +import react, { useState, useEffect } from 'react'; + +import Day from './Day.jsx'; +import '../index.css'; + + + +const dateFormat = (date) => { + let year = parseInt(date.getFullYear()); + let month = parseInt(date.getMonth()+1); + month = (month<10)?`0${month}`:`${month}`; + let day = parseInt(date.getDate()); + day = (day<10)?`0${day}`:`${day}` + + return `${year}-${month}-${day}` +}; + +function Calendar({onDetail, records}){ + const [today, setToday] = useState(new Date()); + const [view, setView] = useState('ring'); + + function onPrev(){ + const newMonth = today.getMonth() - 1; + setToday(new Date(today.getFullYear(), newMonth)); + } + function onNext(){ + const newMonth = today.getMonth() + 1; + setToday(new Date(today.getFullYear(), newMonth)); + } + function handleView(){ + setView((prev) => ((prev==='ring')?'list':'ring')); + } + + let startOfMonth = new Date(today.getFullYear(), today.getMonth(), 1); + let endOfMonth = new Date(today.getFullYear(), today.getMonth()+1, 0); + + let i = 0, j = 0; + let cur = new Date(startOfMonth); + const fullData = []; + + while (cur <= endOfMonth){ + if (j < startOfMonth.getDay()){ + const temp = { date: null }; + fullData.push(temp); + j++ + continue; + } + if (i < records.length && dateFormat(cur) === records[i].date){ + fullData.push(records[i]); + i++ + } + else{ + const temp = { date: dateFormat(cur) }; + fullData.push(temp); + } + cur.setDate(cur.getDate()+1); + //console.log(fullData); + } + + return ( + <> + <div className="row"> + <button onClick={onPrev}>prev</button> + <h1 className="title">{dateFormat(today).slice(0,7)}</h1> + <button onClick={onNext}>next</button> + </div> + <div className="flex-right"> + <button onClick={handleView}>toggle</button> + </div> + <div className="grid"> + <div className="center">일</div> + <div className="center">월</div> + <div className="center">화</div> + <div className="center">수</div> + <div className="center">목</div> + <div className="center">금</div> + <div className="center">토</div> + </div> + <div className="grid"> + <Day + mode={view} + data={fullData} + onDetail={onDetail} /> + </div> + </> + ); +} + +export default Calendar; \ No newline at end of file diff --git a/front/src/components/HabitTracker/Day.jsx b/front/src/components/HabitTracker/Day.jsx new file mode 100644 index 0000000000000000000000000000000000000000..8bf41418f1a5f0b551edb91e4c7a98cf012672a7 --- /dev/null +++ b/front/src/components/HabitTracker/Day.jsx @@ -0,0 +1,116 @@ +import react, { useState, useEffect } from 'react'; + +import '../index.css'; +import Ring from './Ring'; +import ExerciseBlock from './ExerciseBlock'; +import { getGoal } from '../../api/HabitTrackerAPI'; + +function displayWhat(item, mode, goal, doit){ + if (!item.date) + return <></>; + else if (!item.exercises) + return ( + <div id={`${item.date}`} className={`DateCell ${doit}`}> + <span className="center">{item.date.substr(8,2)}</span> + </div> + ); + else if (item.date && item.exercises){ + if (mode === 'ring'){ + return ( + <div id={`${item.date}`} className={`DateCell ${doit}`}> + <span className="center">{item.date.substr(8,2)}</span> + <Ring + data={item} + goal={goal} /> + </div> + ); + } + else if (mode === 'list'){ + return ( + <div id={`${item.date}`} className={`DateCell ${doit}`}> + <span className="center">{item.date.substr(8,2)}</span> + <ExerciseBlock + data={item} /> + </div> + ); + } + else return( + <div> + <span>표시할 정보가 없습니다.</span> + </div> + ); + } + else return( + <div> + <span>표시할 정보가 없습니다.</span> + </div> + ); +} + +function getMax(data){ + let max = 0; + for (let i=0; i<data.length; i++){ + if (!data[i].exercises) continue; + let total = 0; + for (let j=0; j<data[i].exercises.length; j++){ + let time = data[i].exercises[j].video_time; + let min = parseInt(time.slice(0,time.indexOf(':'))); + let sec = parseInt(time.slice(time.indexOf(':')+1)); + total += (min*60+sec); + } + max = (max < total)? total : max; + } + console.log(max); + return max; +} + +function Day({data, mode, onDetail}){ + const [goal, setGoal] = useState({ + goal_weekly: null, + goal_daily: [null, null, null, null, null, null, null], + goal_daily_time: '00:00', + goal_weight: null,} + ); + + useEffect(() => { + fetchGoals(); + }, []) + + const fetchGoals = async () => { + try{ + const data = await getGoal(); + setGoal(data); + } catch (error) { + console.error(error.message); + } + } + const doit = (index) => ((goal.goal_daily[index%7]) ? 'doit' : null); + + let goalTime = goal.goal_daily_time; + if (typeof goalTime === 'string'){ + let total = 0; + let min = parseInt(goalTime.slice(0,goalTime.indexOf(':'))); + let sec = parseInt(goalTime.slice(goalTime.indexOf(':')+1)); + total += (min*60+sec); + goalTime = total; + } + if (mode === 'ring' && goalTime === null){ + goalTime = getMax(data); + } + + function onShow(e){ + onDetail(e.target.id); + } + + return ( + <> + {data.map((item, index) => ( + <div onClick={onShow}> + {displayWhat(item, mode, goalTime, doit(index))} + </div> + ))} + </> + ); +} + +export default Day; \ No newline at end of file diff --git a/front/src/components/HabitTracker/ExerciseBlock.jsx b/front/src/components/HabitTracker/ExerciseBlock.jsx new file mode 100644 index 0000000000000000000000000000000000000000..35b7481a521a140f2d650bac5b38213a1cdd039c --- /dev/null +++ b/front/src/components/HabitTracker/ExerciseBlock.jsx @@ -0,0 +1,59 @@ +import react from 'react'; + +import '../index.css'; + +function Clickable({data}){ + const videoData = data.exercises || data; + + return ( + <> + {videoData.map((item) => ( + <div id="clickBlock" className="block"> + {/* <Thumbnails video_id={item.video_id} /> */} + <div className="space-between"> + <h3 className="simplified">{item.video_title}</h3> + <span> + {item.video_tag} + <br /> + </span> + {/* <span>{secToTime(item.video_length)}</span> */} + {/* <span className="right-text"> + <FontAwesomeIcon icon={faHeart} /> + {item.video_likes} + <br /> + </span> */} + </div> + </div> + ))} + </> + ); +} + +function NonClickable({data}){ + return ( + <> + {data.exercises.map((item) => ( + <div> + <div id={item.date} className="nonclickable"> + {item.video_title} + </div> + </div> + ))} + </> + ); +} + + +function ExerciseBlock({data, mode}){ + return ( + (mode === 'clickable')?( + <Clickable + data={data} /> + ):( + <NonClickable + data={data} /> + ) + ); +} + +export default ExerciseBlock; \ No newline at end of file diff --git a/front/src/components/HabitTracker/Ring.jsx b/front/src/components/HabitTracker/Ring.jsx new file mode 100644 index 0000000000000000000000000000000000000000..727d1c37c9913fcbaf4efa3b71ab930a203e8144 --- /dev/null +++ b/front/src/components/HabitTracker/Ring.jsx @@ -0,0 +1,38 @@ +import react, { useState } from 'react'; + +import '../index.css'; + +function calculPercent(data, goal){ + let total = 0; + for (let i=0; i<data.exercises.length; i++){ + let time = data.exercises[i].video_time; + let min = parseInt(time.slice(0,time.indexOf(':'))); + let sec = parseInt(time.slice(time.indexOf(':')+1)); + total += (min*60+sec); + } + const percent = (total/goal >= 1)? 1 : (total/goal).toFixed(2); + return percent; +} + + +function Ring({data, goal}){ + const percent = calculPercent(data, goal); + const dasharray = [2*Math.PI*10]; + const achieved = (percent === 1)? '#CFFF5E' : '#B87EED'; + + return ( + <div className="center"> + <svg id={data.date} width="10vh" heigth="10vh" viewBox="0 0 30 30"> + <circle id={data.date} + cx="15" cy="15" r="10" fill="none" + stroke={achieved} stroke-width="6" + strokeDasharray={dasharray} + strokeDashoffset={2*Math.PI*10 * (1-percent)} + stroke-linecap="round" + transform="rotate(-90) translate(-30)"/> + </svg> + </div> + ); +} + +export default Ring; \ No newline at end of file diff --git a/front/src/components/HabitTracker/SideBar.jsx b/front/src/components/HabitTracker/SideBar.jsx new file mode 100644 index 0000000000000000000000000000000000000000..4b05abf47ac6c9f5a29da546fd3f22170413a27d --- /dev/null +++ b/front/src/components/HabitTracker/SideBar.jsx @@ -0,0 +1,169 @@ +import react, {useEffect, useState} from 'react'; +import {Link} from 'react-router-dom'; + +import '../index.css'; +import ExerciseBlock from './ExerciseBlock'; +import { getGoal, getUserData } from '../../api/HabitTrackerAPI'; + + + +const exerciseData = + { + date:'2024-11-01', + exercises: [{ + video_title: 'workout1', + video_tag: 'head', + video_time: '10:49' + }, + { + video_title: 'workout6', + video_tag: 'neck', + video_time: '22:49' + },], + }; + + + +function refineGoals(data){ + let result = ''; + for (let i=0; i<7; i++){ + if (data[i] === 1){ + switch(i) { + case 1: + result += '월 ' + break; + case 2: + result += '화 ' + break; + case 3: + result += '수 ' + break; + case 4: + result += '목 ' + break; + case 5: + result += '금 ' + break; + case 6: + result += '토 ' + break; + case 0: + result += '일 ' + break; + default: + break; + } + } + } + return result; +} + +function UserStatus({userData, data}){ + return ( + <div> + <div id="userInfo" className="infoList"> + <div>{userData.user_name}</div> + <div>성별: {userData.user_gender}</div> + <div>나이: {userData.user_age}</div> + </div> + <div className="infoList"> + <h3>목표</h3> + <div>목표 체중: {data.goal_weight}</div> + <div>주간 목표 횟수: {data.goal_weekly}</div> + <div>목표 요일: {refineGoals(data.goal_daily)}</div> + <div>일일 목표 시간: {data.goal_daily_time}</div> + </div> + <button> + <Link className="link large" to='/HabitTracker/Goal'> + 목표 설정 + </Link> + </button> + </div> + ); + }; + +function DailyDetails({day, records, onShow}){ + const data = records.filter(item => item.date === day); + if (data.length > 0){ + return ( + <> + <h2 style={{position:'absolute', top:'150px'}}>{day}</h2> + <ExerciseBlock + data={data[0]} + mode={'clickable'}/> + <button onClick={onShow}>close</button> + </> + ); + } + else{ + return ( + <> + <h2 style={{position:'absolute', top:'150px'}}>{day}</h2> + <h3>표시할 데이터가 없습니다.</h3> + <button onClick={onShow}>close</button> + </> + ); + } +} + +function SideBar({side, day, records, onDetail}){ + const [goal, setGoal] = useState({ + goal_weekly: null, + goal_daily: [null, null, null, null, null, null, null], + goal_daily_time: '00:00', + goal_weight: null,} + ); + const [user, setUser] = useState({ + user_id: null, + user_name: null, + user_gender: 0, + user_age: 0, + user_email: null + }) + + useEffect(() => { + fetchGoals(); + fetchUser(); + }, []) + + const fetchGoals = async () => { + try{ + const data = await getGoal(); + setGoal(data); + } catch (error) { + console.error(error.message); + } + } + const fetchUser = async () => { + try{ + const data = await getUserData(); + setUser(data); + } catch (error) { + console.error(error.message); + } + } + + function onShow(){ + onDetail(false); + } + + return ( + (side === 'right')?( + <div className="sidebar rightSide flex-grow-side"> + <DailyDetails + records={records} + day={day} + onShow={onShow}/> + </div> + ):( + <div className="sidebar leftSide flex-grow-side"> + <UserStatus + userData={user} + data={goal} + /> + </div> + ) + ); +} + +export default SideBar; \ No newline at end of file diff --git a/front/src/components/Header.jsx b/front/src/components/Header.jsx new file mode 100644 index 0000000000000000000000000000000000000000..eba0d34cb78de350b472eba54b614997e5b15a98 --- /dev/null +++ b/front/src/components/Header.jsx @@ -0,0 +1,40 @@ +import react from 'react'; +import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom'; + +import './index.css'; +import {ReactComponent as Logo} from '../assets/logo.svg'; + +function MyPage(props){ + if (props.userId){ + return ( + <span> + <Link className="link large" to='/mypage'>My Page</Link> + <Link className="link large" to='/sign'>Sign Out</Link> + </span> + ); + } + else{ + return ( + <Link className="link large" to='/sign'>Sign in</Link> + ); + } +} + +function Header(){ + + return ( + <div id="header"> + <Link className="logo link large" to='/'> + <Logo width='25pt' height='30pt' fill='#0072CE'/> + <span>Fiturring</span> + </Link> + <Link className="link large" to='/workout'>Workout</Link> + <Link className="link large" to='/habitTracker'>Habit Tracker</Link> + <Link className="link large" to='/routine'>Routine</Link> + <Link className="link large" to='diet'>Diet</Link> + <MyPage /> + </div> + ); +} + +export default Header; \ No newline at end of file diff --git a/front/src/components/index.css b/front/src/components/index.css index 193d192867737f25529b019a0f51f8744f3adda8..b08833fbca870cf1107f6ae537fc405a2d0bd98d 100644 --- a/front/src/components/index.css +++ b/front/src/components/index.css @@ -35,6 +35,28 @@ ul{ list-style-type: none; } + +.grid { + display: grid; + padding: 7px; + grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr; +} + +.nonclickable { + display: flex; + flex-direction: column; + line-height: 1.8; + border-radius: 7px; + border-color: #DFDFE4; + color: black; + background-color: palegoldenrod; + margin: 5px; + text-align: center; +} + +.center{ + display: flex; + .col{ display: flex; flex-direction: column; @@ -45,6 +67,76 @@ ul{ align-items: center; } +.DateCell{ + min-height: 13vh; + background-color: rgb(43, 43, 43); + border-color:aqua; + border: 10px; +} + +.row{ + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.title{ + color: #FFFFFF; +} + +.leftSide{ + left:0; +} + +/* .absolute-left{ + position: absolute; + left: 0; + top: 7em; + display: flex; +} */ + +.rightSide{ + right:0; +} + +.sidebar{ + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + text-align: center; + top: 7rem; + background-color: #242834; + min-height: calc(100vh - 7rem - 7rem); + overflow: hidden; +} + +.flex-right{ + display: flex; + justify-content: flex-end; +} + +.flex-grow-side{ + flex-grow: 1; +} + +.side{ + width: 15%; +} + +.infoList{ + line-height: 1.8; + background-color:#151515; + margin-bottom: 1em; +} + +.doit{ + background-color: #fffbb441; +} + +#clickBlock{ + width: 15em; + #videoSelection{ background-color: #CFFF5E; color: black; diff --git a/front/src/pages/Goal.jsx b/front/src/pages/Goal.jsx new file mode 100644 index 0000000000000000000000000000000000000000..34b74c69d61f4c1be3d74b16b4e0f3558ddae8f4 --- /dev/null +++ b/front/src/pages/Goal.jsx @@ -0,0 +1,135 @@ +import react, { useState, useEffect } from 'react'; + +import SideBar from '../components/HabitTracker/SideBar.jsx'; +import { getGoal, addGoal } from '../api/HabitTrackerAPI.js'; + +function Goal(){ + const [goal, setGoal] = useState([]); + const [weight, setWeight] = useState(); + const [mon, setMon] = useState(false); + const [tue, setTue] = useState(false); + const [wed, setWed] = useState(false); + const [thu, setThu] = useState(false); + const [fri, setFri] = useState(false); + const [sat, setSat] = useState(false); + const [sun, setSun] = useState(false); + const [dailyTime, setDailyTime] = useState(); + const [weeklyCount, setWeeklyCount] = useState(0); + + useEffect(() => { + setWeeklyCount(mon+tue+wed+thu+fri+sat+sun); + }, [mon, tue, wed, thu, fri, sat, sun]); + + useEffect(() => { + fetchGoals(); + }, []) + + const fetchGoals = async () => { + try{ + const data = await getGoal(); + setWeight(data.goal_weight); + setDailyTime(data.goal_daily_time); + setSun(data.goal_daily[0]); + setMon(data.goal_daily[1]); + setTue(data.goal_daily[2]); + setWed(data.goal_daily[3]); + setThu(data.goal_daily[4]); + setFri(data.goal_daily[5]); + setSat(data.goal_daily[6]); + setWeeklyCount(data.goal_weekly); + } catch (error) { + console.error(error.message); + } + } + function handleDTChange(e){ + const newvalue = e.target.value; + if (newvalue.indexOf(":") === -1){ + alert("분:초 단위로 입력해주세요"); + return; + } + setDailyTime(e.target.value); + } + function handleWeightChange(e){ + setWeight(e.target.value); + } + function handleClick(e){ + e.preventDefault(); + console.log(e); + + const target = e.target.id; + switch(target) { + case 'Mon': + setMon(prev => !prev); + break; + case 'Tue': + setTue(prev => !prev); + break; + case 'Wed': + setWed(prev => !prev); + break; + case 'Thu': + setThu(prev => !prev); + break; + case 'Fri': + setFri(prev => !prev); + break; + case 'Sat': + setSat(prev => !prev); + break; + case 'Sun': + setSun(prev => !prev); + break; + default: + break; + } + } + function handleSubmit(e){ + e.preventDefault(); + const data = { + goal_weekly: weeklyCount, + goal_daily: [sun, mon, tue, wed, thu, fri, sat], + goal_daily_time: dailyTime, + goal_weight: weight + } + addGoal(data); + } + return ( + <div className='row flex'> + <SideBar + side={"left"} + /> + + <form id="GoalForm" className='flex-grow-main' onSubmit={handleSubmit}> + <label id="title">목표 설정</label> + <label>목표 체중</label> + <input id="weight" type="number" name="goal_weight" onChange={handleWeightChange} value={weight}></input> + + <div className="row" > + <button id="Sun" className="circle" onClick={handleClick} + style={{backgroundColor:(sun)?'#333333':null}}> 일 </button> + <button id="Mon" className="circle" onClick={handleClick} + style={{backgroundColor:(mon)?'#333333':null}}> 월 </button> + <button id="Tue" className="circle" onClick={handleClick} + style={{backgroundColor:(tue)?'#333333':null}}> 화 </button> + <button id="Wed" className="circle" onClick={handleClick} + style={{backgroundColor:(wed)?'#333333':null}}> 수 </button> + <button id="Thu" className="circle" onClick={handleClick} + style={{backgroundColor:(thu)?'#333333':null}}> 목 </button> + <button id="Fri" className="circle" onClick={handleClick} + style={{backgroundColor:(fri)?'#333333':null}}> 금 </button> + <button id="Sat" className="circle" onClick={handleClick} + style={{backgroundColor:(sat)?'#333333':null}}> 토 </button> + </div> + <label>일일 목표 운동 시간</label> + <input id="DailyTime" type="text" name="goal_daily_time" onChange={handleDTChange} value={dailyTime}></input> + + <label>주간 목표 횟수</label> + <input id="weeklyCount" type="text" name="goal_weekly" value={weeklyCount}></input> + + <button type="submit">저장</button> + </form> + </div> + ); +} + +export default Goal; \ No newline at end of file diff --git a/front/src/pages/HabitTracker.jsx b/front/src/pages/HabitTracker.jsx new file mode 100644 index 0000000000000000000000000000000000000000..e5d2d8a58d8272f877c8857d45d95abef83666dc --- /dev/null +++ b/front/src/pages/HabitTracker.jsx @@ -0,0 +1,57 @@ +import react, { useState, useEffect } from 'react'; +import Calendar from '../components/HabitTracker/Calendar'; +import SideBar from '../components/HabitTracker/SideBar'; +import { getMonthlyRecord } from '../api/HabitTrackerAPI' + + +function HabitTracker(){ + const [detail, setDetail] = useState(false); + const [records, setRecords] = useState({}); + + + useEffect(() => { + fetchMonthlyRecords(); + }, []) + const fetchMonthlyRecords = async () => { + try{ + const data = await getMonthlyRecord(); + setRecords(data); + } catch (error) { + console.error(error.message); + } + } + + function onDetail(e){ + setDetail(e); + } + function deleteDetail(){ + setDetail(false); + } + function userStatus(){ + + } + + return ( + <div className="flex"> + <SideBar + side={"left"}/> + + <div className={`flex-grow-main `}> + <Calendar + records={records} + onDetail={onDetail} + /> + </div> + + {detail && ( + <SideBar + side={"right"} + records={records} + day={detail} + onDetail={deleteDetail}/> + )} + </div> + ); +} + +export default HabitTracker; diff --git a/front/src/pages/Home.jsx b/front/src/pages/Home.jsx index f0e2638cacc1e4f886a72c4d30a0772a63a266eb..8c8be24cea9b7252577ed84d2680e7b882459b82 100644 --- a/front/src/pages/Home.jsx +++ b/front/src/pages/Home.jsx @@ -2,7 +2,9 @@ import react from 'react'; function Home(){ return ( - <h1>Home</h1> + <div> + <h1>Home</h1> + </div> ); } diff --git a/front/src/pages/index.css b/front/src/pages/index.css index a084e251fb90de4cdb67e887efaa2469e7481cd9..988ebc9e8dbde6195161436f3700461a4f86e91a 100644 --- a/front/src/pages/index.css +++ b/front/src/pages/index.css @@ -83,6 +83,30 @@ button{ font-size:medium; } + +.padding{ + padding: 20px; +} + +.flex{ + display: flex; + width: 100vw; +} + +.flex-grow-main{ + flex-grow: 15; +} + +.circle{ + width: 3em; +} + +input[value] { + color: black; + font-size:medium; + text-align: center; +} + #workout{ display: grid; grid-template-columns: 1fr 3fr 6fr;