diff --git a/react-whenMeet/src/components/CalendarWeek.jsx b/react-whenMeet/src/components/CalendarWeek.jsx index 9262d2ec45310c1d02fa17c4350f8adb77b5c764..c57b4ade0abbd46f8eaf8df9428cc0e0c2752097 100644 --- a/react-whenMeet/src/components/CalendarWeek.jsx +++ b/react-whenMeet/src/components/CalendarWeek.jsx @@ -1,50 +1,175 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import "../styles/CalendarWeek.css"; -const hours = [...Array(24).keys()].map((i) => `${i}:00`); // 0:00 to 23:00 -const days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; -const CalendarWeek = () => { +const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + +const CalendarWeek = ({ + participants, + startDate, + endDate, + maxParticipants, + hoveredInfo, + setHoveredInfo, +}) => { + const [currentWeekIndex, setCurrentWeekIndex] = useState(0); + const [weeks, setWeeks] = useState([]); const [schedule, setSchedule] = useState({}); - const toggleHour = (day, hour) => { - const daySchedule = schedule[day] || []; - if (daySchedule.includes(hour)) { - setSchedule({ - ...schedule, - [day]: daySchedule.filter((h) => h !== hour), - }); - } else { - setSchedule({ - ...schedule, - [day]: [...daySchedule, hour], + const handlePrevWeek = () => { + setCurrentWeekIndex(Math.max(0, currentWeekIndex - 1)); + }; + + const handleNextWeek = () => { + setCurrentWeekIndex(Math.min(weeks.length - 1, currentWeekIndex + 1)); + }; + + useEffect(() => { + const start = new Date(startDate); + start.setDate( + start.getDate() - (start.getDay() === 0 ? 6 : start.getDay() - 1) + ); + const end = new Date(endDate); + + let weeksTemp = []; + while (start <= end) { + const weekStart = new Date(start); + const weekDates = Array.from({ length: 7 }).map((_, index) => { + const date = new Date(weekStart); + date.setDate(date.getDate() + index); + return date; }); + + weeksTemp.push(weekDates); + start.setDate(start.getDate() + 7); } + setWeeks(weeksTemp); + + let newSchedule = {}; + participants.forEach((participant) => { + participant.availableSchedules.forEach( + ({ availableDate, availableTimes }) => { + const date = new Date(availableDate); + availableTimes.forEach((time) => { + const hour = Math.floor(time / 2); + const minute = (time % 2) * 30; + const timeString = `${hour.toString().padStart(2, "0")}:${minute + .toString() + .padStart(2, "0")}`; + const dateString = `${date.getFullYear()}-${(date.getMonth() + 1) + .toString() + .padStart(2, "0")}-${date.getDate().toString().padStart(2, "0")}`; + if (!newSchedule[dateString]) { + newSchedule[dateString] = []; + } + newSchedule[dateString].push({ + time: timeString, + name: participant.name, + }); + }); + } + ); + }); + setSchedule(newSchedule); + }, [participants, startDate, endDate]); + + const calculateOpacity = (dateString, timeString) => { + const availableCount = + schedule[dateString]?.filter((s) => s.time === timeString).length || 0; + return 100 - (availableCount / maxParticipants) * 100; + }; + + const handleMouseEnter = (dateString, timeString) => { + const availableParticipants = + schedule[dateString] + ?.filter((s) => s.time === timeString) + .map((s) => s.name) || []; + setHoveredInfo({ + date: dateString, + time: timeString, + participants: availableParticipants, + }); }; + const handleMouseLeave = () => { + setHoveredInfo(null); + }; + + const weekDates = weeks[currentWeekIndex] || []; + return ( - <table className="calendar-container"> - <thead> - <tr> - {["", ...days].map((day) => ( - <th key={day}>{day}</th> - ))} - </tr> - </thead> - <tbody> - {hours.map((hour) => ( - <tr key={hour}> - <td>{hour}</td> - {days.map((day) => ( - <td - key={day} - className={schedule[day]?.includes(hour) ? "selected" : ""} - onClick={() => toggleHour(day, hour)} - /> + <div> + <button onClick={handlePrevWeek} disabled={currentWeekIndex === 0}> + Prev Week + </button> + <button + onClick={handleNextWeek} + disabled={currentWeekIndex === weeks.length - 1} + > + Next Week + </button> + <table className="calendar-container"> + <thead> + <tr> + <th>Time</th> + {weekDates.map((date) => ( + <th key={date.toISOString()}> + {date.getMonth() + 1}/{date.getDate()} ({days[date.getDay()]}) + </th> ))} </tr> - ))} - </tbody> - </table> + </thead> + <tbody> + {[...Array(48).keys()].map((timeSlot) => { + const hour = Math.floor(timeSlot / 2); + const minute = (timeSlot % 2) * 30; + return ( + <tr key={timeSlot}> + <td> + {hour.toString().padStart(2, "0") + + ":" + + minute.toString().padStart(2, "0")} + </td> + {weekDates.map((date) => { + const dateString = `${date.getFullYear()}-${( + date.getMonth() + 1 + ) + .toString() + .padStart(2, "0")}-${date + .getDate() + .toString() + .padStart(2, "0")}`; + const timeString = `${hour + .toString() + .padStart(2, "0")}:${minute.toString().padStart(2, "0")}`; + const opacity = calculateOpacity(dateString, timeString); + const cellStyle = { opacity: `${opacity}%` }; + return ( + <td + key={`${date.toISOString()}-${timeString}`} + style={cellStyle} + onMouseEnter={() => + handleMouseEnter(dateString, timeString) + } + onMouseLeave={handleMouseLeave} + /> + ); + })} + </tr> + ); + })} + </tbody> + </table> + {hoveredInfo && ( + <div className="possibleMan" style={{ textAlign: "center" }}> + <strong>가능한 사람:</strong> + <ul> + {hoveredInfo.participants.map((name) => ( + <li key={name}>{name}</li> + ))} + </ul> + </div> + )} + </div> ); }; diff --git a/react-whenMeet/src/components/ResultEndForm.jsx b/react-whenMeet/src/components/ResultEndForm.jsx index a46f3ababc37010e6a704790e08f0cd6264dc771..466bda463699693867e09e07bd21443164c8ef56 100644 --- a/react-whenMeet/src/components/ResultEndForm.jsx +++ b/react-whenMeet/src/components/ResultEndForm.jsx @@ -3,61 +3,148 @@ import React, { useState } from "react"; import "../styles/ResultEnd.css"; import "../styles/CalendarWeek.css"; export default function ResultEndForm() { - const [title, setTitle] = useState("Title 예시"); + const meetingData = { + id: "1ag123jkF1", + title: "제목", + purpose: "STUDY", + startDate: "2023-12-20", + endDate: "2024-1-07", + currentParticipants: 2, + maxParticipants: 4, + voteExpiresAt: "2023-12-25T03:24:00", + isClosed: false, + participants: [ + { + name: "test1", + availableSchedules: [ + { + availableDate: "2023-12-20", + availableTimes: [6, 7, 8, 9, 14, 15, 16, 17], + }, + { + availableDate: "2023-12-21", + availableTimes: [16, 17], + }, + { + availableDate: "2023-12-22", + availableTimes: [24, 25, 26, 27, 28, 40, 41, 42], + }, + ], + }, + { + name: "test2", + availableSchedules: [ + { + availableDate: "2023-12-22", + availableTimes: [38, 40], + }, + ], + }, + { + name: "test3", + availableSchedules: [ + { + availableDate: "2023-12-22", + availableTimes: [38, 40, 41, 42], + }, + ], + }, + { + name: "test4", + availableSchedules: [ + { + availableDate: "2023-12-22", + availableTimes: [38], + }, + ], + }, + { + name: "test5", + availableSchedules: [ + { + availableDate: "2023-12-22", + availableTimes: [38], + }, + ], + }, + ], + }; + const [title, setTitle] = useState(meetingData.title); const [resultTime, setresultTIme] = useState("00:37:30"); - const [completedPeopleNum, setcompletedPeopleNum] = useState(3); + const [completedPeopleNum, setcompletedPeopleNum] = useState( + meetingData.currentParticipants + ); const [selectedDate, setSelectedDate] = useState(""); - //const [possibleDates, setpossibleDate] = useState(""); const possibleDates = ["23.07.01 ~~~", "23.07.02 ~~~", "23.07.03 ~~~"]; - const [completedTasks, setCompletedTasks] = useState(3); - const [totalTasks, setTotalTasks] = useState(7); - const [timeLeft, setTimeLeft] = useState("00:37:30"); + const [hoveredInfo, setHoveredInfo] = useState(null); + const handleDateChange = (event) => { setSelectedDate(event.target.value); }; return ( - <div style={{ textAlign: "center", width: "50%" }}> - <h1 className="title-box">{title}</h1> - <p>투표가 종료되었습니다.</p> - <p style={{ color: "blue" }}>약속 시간은 {resultTime}입니다.</p> - <div> - <h2>총 참여한 인원수</h2> - <p>{completedPeopleNum}</p> - <form className="form-container"> - {possibleDates.map((date, index) => ( - <label key={index}> - <input - type="radio" - name="date" - value={date} - checked={selectedDate === date} - onChange={handleDateChange} - /> - {date} - </label> - ))} - </form> - <button - style={{ marginTop: "20px", padding: "10px 20px" }} - onClick={() => console.log(selectedDate)} - > - 이 시간으로 정했어요 - </button> - <button style={{ marginTop: "10px", padding: "10px 20px" }}> - 랜덤으로 약속 시간 확정하기 - </button> - </div> - <CalendarWeek className="calander" /> - <span - className="possibleMan" - style={{ - position: "absolute", - marginTop: "100px", - marginLeft: "100px", - }} - > - <div style={{ textAlign: "center" }}>가능한 사람</div> + <div + style={{ textAlign: "center", display: "flex", flexDirection: "column" }} + > + <span className="row1"> + <h1 className="title-box">{title}</h1> + <p>투표가 종료되었습니다.</p> + <p style={{ color: "blue" }}>약속 시간은 {resultTime}입니다.</p> + <div> + <h2>총 참여한 인원수</h2> + <p>{completedPeopleNum}</p> + <form className="form-container"> + {possibleDates.map((date, index) => ( + <label key={index}> + <input + type="radio" + name="date" + value={date} + checked={selectedDate === date} + onChange={handleDateChange} + /> + {date} + </label> + ))} + </form> + <button + style={{ marginTop: "20px", padding: "10px 20px" }} + onClick={() => console.log(selectedDate)} + > + 이 시간으로 정했어요 + </button> + <button style={{ marginTop: "10px", padding: "10px 20px" }}> + 랜덤으로 약속 시간 확정하기 + </button> + </div> + <span style={{ display: "flex", flexDirection: "row" }}> + <div className="calander-container"> + <CalendarWeek + participants={meetingData.participants} + startDate={meetingData.startDate} + endDate={meetingData.endDate} + maxParticipants={meetingData.maxParticipants} + hoveredInfo={hoveredInfo} + setHoveredInfo={setHoveredInfo} + /> + </div> + <div className="row2"> + <span className="possible"> + {hoveredInfo && ( + <div style={{ textAlign: "center" }}> + <strong> + {hoveredInfo.date} {hoveredInfo.time}에 가능한 사람: + </strong> + <ul> + {hoveredInfo.participants.map((name) => ( + <li key={name}>{name}</li> + ))} + </ul> + </div> + )} + </span> + </div> + </span> </span> </div> ); diff --git a/react-whenMeet/src/components/ResultMakeForm.js b/react-whenMeet/src/components/ResultMakeForm.js index 7f34f58fdb5becbd86b39e8f351b8aae8182ebcb..6d9ea07d117b719f401838f4f11653112bfc5919 100644 --- a/react-whenMeet/src/components/ResultMakeForm.js +++ b/react-whenMeet/src/components/ResultMakeForm.js @@ -1,47 +1,180 @@ //결과 확인 페이지 -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import CalendarWeek from "./CalendarWeek"; import { useNavigate } from "react-router-dom"; +import "../styles/ResultMake.css"; function ResultMakeForm() { - const [title, setTitle] = useState("Title 예시"); - const [completedTasks, setCompletedTasks] = useState(3); - const [totalTasks, setTotalTasks] = useState(7); - const [timeLeft, setTimeLeft] = useState("00:37:30"); + const meetingData = { + id: "1ag123jkF1", + title: "제목", + purpose: "STUDY", + startDate: "2023-12-20", + endDate: "2024-1-07", + currentParticipants: 2, + maxParticipants: 4, + voteExpiresAt: "2023-12-25T03:24:00", + isClosed: false, + participants: [ + { + name: "test1", + availableSchedules: [ + { + availableDate: "2023-12-20", + availableTimes: [6, 7, 8, 9, 14, 15, 16, 17], + }, + { + availableDate: "2023-12-21", + availableTimes: [16, 17], + }, + { + availableDate: "2023-12-22", + availableTimes: [24, 25, 26, 27, 28, 40, 41, 42], + }, + ], + }, + { + name: "test2", + availableSchedules: [ + { + availableDate: "2023-12-22", + availableTimes: [38, 40], + }, + ], + }, + { + name: "test3", + availableSchedules: [ + { + availableDate: "2023-12-22", + availableTimes: [38, 40, 41, 42], + }, + ], + }, + { + name: "test4", + availableSchedules: [ + { + availableDate: "2023-12-22", + availableTimes: [38], + }, + ], + }, + { + name: "test5", + availableSchedules: [ + { + availableDate: "2023-12-22", + availableTimes: [38], + }, + ], + }, + ], + }; + const [title, setTitle] = useState(meetingData.title); + const [currentParticipants, setcurrentParticipands] = useState( + meetingData.currentParticipants + ); + const [maxParticipants, setmaxParticipants] = useState( + meetingData.maxParticipants + ); + const [timeLeft, setTimeLeft] = useState(""); const navigate = useNavigate(); + const [hoveredInfo, setHoveredInfo] = useState(null); // 타이머를 시작하고 관리하는 로직 + useEffect(() => { + const calculateTimeLeft = () => { + const now = new Date(); + const voteExpires = new Date(meetingData.voteExpiresAt); + const difference = voteExpires - now; + + let timeLeft = {}; + + if (difference > 0) { + timeLeft = { + days: Math.floor(difference / (1000 * 60 * 60 * 24)), + hours: Math.floor((difference / (1000 * 60 * 60)) % 24), + minutes: Math.floor((difference / 1000 / 60) % 60), + seconds: Math.floor((difference / 1000) % 60), + }; + } + + return timeLeft; + }; + + const updateTimer = () => { + const newTimeLeft = calculateTimeLeft(); + const formattedTime = `${ + newTimeLeft.days ? newTimeLeft.days + "일 " : "" + }${newTimeLeft.hours ? newTimeLeft.hours + "시간 " : ""}${ + newTimeLeft.minutes ? newTimeLeft.minutes + "분 " : "" + }${newTimeLeft.seconds ? newTimeLeft.seconds + "초" : ""}`; + setTimeLeft(formattedTime); + }; + + updateTimer(); + const timerId = setInterval(updateTimer, 1000); + + return () => clearInterval(timerId); // 컴포넌트가 언마운트될 때 인터벌을 정리합니다. + }, [meetingData.voteExpiresAt]); const handleEdit = () => { navigate("/meetinginfo/linkpage"); }; return ( - <div> - <h1 className="title-box">{title}</h1> - <div> - 현재 완료한 일업수 {completedTasks} / {totalTasks} + <> + <div className="column-container"> + <h1 className="title-box">{title}</h1> + <div> + 현재 완료한 인원수 {currentParticipants} / {maxParticipants} + </div> + <div>종료까지 남은 시간 {timeLeft}</div> + <button onClick={handleEdit}>수정하기</button> + <button + onClick={() => { + navigate("/resultend"); + }} + > + 투표 종료하기 + </button> </div> - <div>종료까지 남은 시간 {timeLeft}</div> - <button onClick={handleEdit}>수정하기</button> - <button - onClick={() => { - navigate("/resultend"); - }} - > - 투표 종료하기 - </button> - <CalendarWeek /> - <span className="mostTime"> - <div style={{ textAlign: "center" }}> - 가장 많은 사람들이 가능한 일정 + <span className="flex-row"> + <div className="calander-container"> + <CalendarWeek + participants={meetingData.participants} + startDate={meetingData.startDate} + endDate={meetingData.endDate} + maxParticipants={meetingData.maxParticipants} + hoveredInfo={hoveredInfo} + setHoveredInfo={setHoveredInfo} + /> + </div> + + <div className="row-container"> + <span className="mostTime"> + <div style={{ textAlign: "center" }}> + 가장 많은 사람들이 가능한 일정 + </div> + <ol>//일정 5개 나열</ol> + </span> + <span className="possibleMan"> + {hoveredInfo && ( + <div style={{ textAlign: "center" }}> + <strong> + {hoveredInfo.date} {hoveredInfo.time}에 가능한 사람: + </strong> + <ul> + {hoveredInfo.participants.map((name) => ( + <li key={name}>{name}</li> + ))} + </ul> + </div> + )} + </span> </div> - <ol>//일정 5개 나열</ol> - </span> - <span className="possibleMan"> - <div style={{ textAlign: "center" }}>가능한 사람</div> </span> - </div> + </> ); } - export default ResultMakeForm; diff --git a/react-whenMeet/src/routes/ResultEnd.jsx b/react-whenMeet/src/routes/ResultEnd.jsx index 6a9caacb875013647f346280425893b71eea4c4d..f7c0b486a0571a3a2e691ea82073ab4fdf320341 100644 --- a/react-whenMeet/src/routes/ResultEnd.jsx +++ b/react-whenMeet/src/routes/ResultEnd.jsx @@ -3,7 +3,9 @@ import "../styles/HomeMake.css"; function ResultEnd() { return ( - <div className="center-container"> + <div + style={{ display: "flex", alignItems: "center", flexDirection: "column" }} + > <ResultEndForm /> </div> ); diff --git a/react-whenMeet/src/routes/ResultMake.js b/react-whenMeet/src/routes/ResultMake.js index d9c30bf09abc4ac2ab216212d30b46d7dbf73496..bbef20d6f9f0de341e027df436b4ba0d3546be1e 100644 --- a/react-whenMeet/src/routes/ResultMake.js +++ b/react-whenMeet/src/routes/ResultMake.js @@ -1,9 +1,10 @@ import ResultMakeForm from "../components/ResultMakeForm"; -import "../styles/HomeMake.css"; function ResultMake() { return ( - <div className="center-container"> + <div + style={{ display: "flex", alignItems: "center", flexDirection: "column" }} + > <ResultMakeForm /> </div> ); diff --git a/react-whenMeet/src/styles/HomeMake.css b/react-whenMeet/src/styles/HomeMake.css index 5ea58f7852096c5809a766c5a13ac2c9a828a3bc..12adca798b45cfe1fd65f61b80adbac21a1730c5 100644 --- a/react-whenMeet/src/styles/HomeMake.css +++ b/react-whenMeet/src/styles/HomeMake.css @@ -35,7 +35,7 @@ h2 { margin: 0; } -.timeStartEnd{ +.timeStartEnd { display: flex; align-items: center; justify-content: space-between; @@ -67,13 +67,13 @@ button:hover { background-color: #2980b9; } -.header{ +.header { display: flex; gap: 100px; } -.purpose-selector{ - margin : 20px; +.purpose-selector { + margin: 20px; } .calendarTable { margin: 0 auto; /* 가운데 정렬을 위해 margin을 auto로 설정합니다. */ @@ -88,27 +88,6 @@ button:hover { color: black; /* 검정색으로 글자색 지정 */ } -.mostTime { - position: absolute; - margin-top: 200px; - margin-left: 20px; - width: 20em; - border-width: 1px; - border-color: black; - border-style: solid; - padding: 20px; -} -.possibleMan { - position: absolute; - margin-left: 20px; - margin-top: 800px; - width: 20em; - border-width: 1px; - border-color: black; - border-style: solid; - padding: 20px; -} - .title-box { text-align: center; border-width: 1px; diff --git a/react-whenMeet/src/styles/ResultEnd.css b/react-whenMeet/src/styles/ResultEnd.css index 56d58060be6e198ad2e52d261da30af1b71033f6..f25c598989dc3905dd92bfcb1e2ea8bd091171a7 100644 --- a/react-whenMeet/src/styles/ResultEnd.css +++ b/react-whenMeet/src/styles/ResultEnd.css @@ -6,5 +6,18 @@ .calendar { border: 1px solid; - padding : 10px; + padding: 10px; +} +.possible { + margin-left: 0px; + position: fixed; + width: 20em; + border-width: 1px; + border-color: black; + border-style: solid; + padding: 20px; +} +.row { + display: flex; + justify-content: center; } diff --git a/react-whenMeet/src/styles/ResultMake.css b/react-whenMeet/src/styles/ResultMake.css new file mode 100644 index 0000000000000000000000000000000000000000..154467e917257f6480783c34c1c420d96aa5939a --- /dev/null +++ b/react-whenMeet/src/styles/ResultMake.css @@ -0,0 +1,39 @@ +.column-container { + display: flex; + flex-direction: column; +} + +.row-container { + display: flex; +} + +.flex-row { + display: flex; + flex-direction: row; +} + +.calendar-container { + margin-right: 30px; +} + +.mostTime { + background-color: white; + position: fixed; + margin-left: 20px; + width: 20em; + border-width: 1px; + border-color: black; + border-style: solid; + padding: 20px; +} +.possibleMan { + background-color: white; + position: fixed; + margin-left: 20px; + margin-top: 200px; + width: 20em; + border-width: 1px; + border-color: black; + border-style: solid; + padding: 20px; +}