diff --git a/react-whenMeet/Dockerfile b/react-whenMeet/Dockerfile index 33b3773160ad0f4535bda962033684f78e775832..b6fab58f44c620a47f39c9f9b132fd34f68c8a5b 100644 --- a/react-whenMeet/Dockerfile +++ b/react-whenMeet/Dockerfile @@ -3,5 +3,5 @@ WORKDIR /app COPY package.json ./ RUN npm install COPY . . -EXPOSE 80 +EXPOSE 3001 CMD ["npm", "start"] \ No newline at end of file diff --git a/react-whenMeet/package.json b/react-whenMeet/package.json index dafb6ea7b18bff19ba9b489f9521e02e3c51cfff..600f87f5fb7656835882ba780874569b60d46f25 100644 --- a/react-whenMeet/package.json +++ b/react-whenMeet/package.json @@ -19,7 +19,7 @@ "web-vitals": "^2.1.4" }, "scripts": { - "start": "react-scripts start", + "start": "export PORT=3001 && react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" diff --git a/react-whenMeet/src/components/CalendarWeek.jsx b/react-whenMeet/src/components/CalendarWeek.jsx index 6340e2bd8c112e7ae04d67e822ada015d9d5889c..604d8f0e3dec2f47abf7c1374a7e547d8039d0d0 100644 --- a/react-whenMeet/src/components/CalendarWeek.jsx +++ b/react-whenMeet/src/components/CalendarWeek.jsx @@ -115,7 +115,7 @@ const CalendarWeek = ({ const weekDates = weeks[currentWeekIndex] || []; return ( - <div className="wrap"> + <div className="calendarWeekContainer wrap"> <div className="button-container"> <button onClick={handlePrevWeek} disabled={currentWeekIndex === 0}> Prev Week diff --git a/react-whenMeet/src/components/ResultMakeForm.js b/react-whenMeet/src/components/ResultMakeForm.js index b44a2534d85026977de580b1096124cc5fd2a3a1..d0fdb575946efd63c16796793c9a3660cfae897f 100644 --- a/react-whenMeet/src/components/ResultMakeForm.js +++ b/react-whenMeet/src/components/ResultMakeForm.js @@ -13,7 +13,70 @@ function ResultMakeForm() { const [hoveredInfo, setHoveredInfo] = useState(null); const [isLoading, setIsLoading] = useState(true); const [isModalOpen, setIsModalOpen] = useState(false); + const [mostAvailableDates, setMostAvailableDates] = useState([]); + const [topAvailableDates, setTopAvailableDates] = useState([]); + useEffect(() => { + if (meetingData && meetingData.participants) { + calculateTopAvailableDates(); + } + }, [meetingData]); + + const calculateTopAvailableDates = () => { + let dateAvailability = {}; + + meetingData.participants.forEach((participant) => { + participant.availableSchedules.forEach((schedule) => { + if (!dateAvailability[schedule.availableDate]) { + dateAvailability[schedule.availableDate] = {}; + } + schedule.availableTimes.forEach((time) => { + if (!dateAvailability[schedule.availableDate][time]) { + dateAvailability[schedule.availableDate][time] = 0; + } + dateAvailability[schedule.availableDate][time]++; + }); + }); + }); + + let dateCounts = Object.entries(dateAvailability).map(([date, times]) => { + let maxCount = Math.max(...Object.values(times)); + let count = Object.values(times).filter((val) => val === maxCount).length; + return { date, count, maxCount }; + }); + + dateCounts.sort((a, b) => b.maxCount - a.maxCount || b.count - a.count); + + setTopAvailableDates(dateCounts.slice(0, 5)); + }; + + useEffect(() => { + if (meetingData && meetingData.participants) { + calculateMostAvailableDates(); + } + }, [meetingData]); + + const calculateMostAvailableDates = () => { + let dateAvailabilityCount = {}; + + meetingData.participants.forEach((participant) => { + participant.availableSchedules.forEach((schedule) => { + if (!dateAvailabilityCount[schedule.availableDate]) { + dateAvailabilityCount[schedule.availableDate] = new Set(); + } + schedule.availableTimes.forEach((time) => { + dateAvailabilityCount[schedule.availableDate].add(time); + }); + }); + }); + + const sortedDates = Object.entries(dateAvailabilityCount) + .map(([date, times]) => ({ date, count: times.size })) + .sort((a, b) => b.count - a.count) + .slice(0, 5); + + setMostAvailableDates(sortedDates); + }; useEffect(() => { const fetchMeetingData = async () => { setIsLoading(true); @@ -141,15 +204,15 @@ function ResultMakeForm() { <h1 className="title-box">{meetingData?.title}</h1> {meetingData.maxParticipants && ( <div> - 현재 완료한 인원수 {meetingData?.currentParticipants} /{" "} + 현재 완료한 인원수 : {meetingData?.currentParticipants} /{" "} {meetingData?.maxParticipants} </div> )} {!meetingData.maxParticipants && ( - <div>현재 완료한 인원수 {meetingData?.currentParticipants}</div> + <div>현재 완료한 인원수 : {meetingData?.currentParticipants}</div> )} - <div>종료까지 남은 시간 {timeLeft}</div> + <div>종료까지 남은 시간 : {timeLeft}</div> <button onClick={handleEdit}>수정하기</button> <button onClick={closeMeeting}>투표 종료하기</button> </div> @@ -174,33 +237,42 @@ function ResultMakeForm() { onRequestClose={handleModalClose} onSubmit={handlePasswordSubmit} /> - <span className="mostTime"> - <div style={{ textAlign: "center" }}> - 가장 많은 사람들이 가능한 일정 - </div> - <ol>//일정 5개 나열</ol> - </span> - <span className="possible"> - {!hoveredInfo && ( - <div> - <strong>가능한 사람들이 표시됩니다.</strong> - <p>마우스를 달력 위에 올려보세요!</p> - </div> - )} - - {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 className="flex-bottom-container"> + <span className="mostTime"> + <strong style={{ textAlign: "center" }}> + 가장 많은 사람들이 가능한 일정 + </strong> + <ol> + {topAvailableDates.map((dateInfo, index) => ( + <li key={index}> + {dateInfo.date} ({dateInfo.maxCount}명이 가능한 시간대:{" "} + {dateInfo.count}개 ) + </li> + ))} + </ol> + </span> + <span className="possible"> + {!hoveredInfo && ( + <div> + <strong>가능한 사람들이 표시됩니다.</strong> + <p>마우스를 달력 위에 올려보세요!</p> + </div> + )} + + {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> </> ); } diff --git a/react-whenMeet/src/styles/CalendarWeek.css b/react-whenMeet/src/styles/CalendarWeek.css index 406b79c7d99199e75d9ab92cea02500cf7b8c6e8..f7007bdbcfa474096910e62ba38122a2b6949fdd 100644 --- a/react-whenMeet/src/styles/CalendarWeek.css +++ b/react-whenMeet/src/styles/CalendarWeek.css @@ -1,51 +1,66 @@ -.calendar-container { +.calendarWeekContainer .calendar-container { margin-top: 20px; width: 100%; background-color: #5fbdff; } -.button-container { + +.calendarWeekContainer .button-container { display: flex; justify-content: space-between; } -.button-container button:first-child { - margin-right: 20px; + +.calendarWeekContainer .button-container button:first-child { + margin-right: 10%; + margin-left: 10%; } -.button-container button:last-child { - margin-left: 20px; + +.calendarWeekContainer .button-container button:last-child { + margin-right: 10%; + margin-left: 10%; } -.wrap { + +.calendarWeekContainer .wrap { width: 80%; } -table { + +.calendarWeekContainer table { width: 100%; border-collapse: collapse; text-align: center; margin-bottom: 4%; } -th, -td { +.calendarWeekContainer th, +.calendarWeekContainer td { border: 1px solid #ddd; padding: 10px; } -th { +.calendarWeekContainer th { background-color: #f4f4f4; } -td { +.calendarWeekContainer td { background-color: #f4f4f4; cursor: pointer; transition: background-color 0.3s ease, transform 0.3s ease, - box-shadow 0.3s ease; /* 부드러운 애니메이션 효과 */ + box-shadow 0.3s ease; } -td:hover { + +.calendarWeekContainer td:hover { background-color: aqua; - transform: scale(1.05); /* 셀을 약간 확대 */ - box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); /* 셀에 그림자 추가 */ - border: 1px solid #5fbdff; /* 셀 주위에 테두리 추가 */ + transform: scale(1.05); + box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); + border: 1px solid #5fbdff; +} + +.calendarWeekContainer .selected { + background-color: #e0ffe0; } -.selected { - background-color: #e0ffe0; /* 선택된 셀의 배경색 */ +@media screen and (max-width: 768px) { + .calendarWeekContainer .wrap { + display: flex; + width: 100%; + } } diff --git a/react-whenMeet/src/styles/ResultEnd.css b/react-whenMeet/src/styles/ResultEnd.css index 9b944aef28649883f68f10ddd03efbb25fd3afd8..02692e2138f8850c3fd15639fdfe48288ac0e20c 100644 --- a/react-whenMeet/src/styles/ResultEnd.css +++ b/react-whenMeet/src/styles/ResultEnd.css @@ -9,11 +9,11 @@ } .form-container label { display: flex; - align-items: center; /* 라디오 버튼과 텍스트를 세로 중앙에 맞춤 */ - margin: 20px 0; /* 라벨 간에 여백을 추가 */ + align-items: center; + margin: 20px 0; } .form-container input[type="radio"] { - margin-right: 10px; /* 라디오 버튼과 텍스트 사이의 공간 조정 */ + margin-right: 10px; width: 20%; } @@ -24,11 +24,9 @@ .possible { position: fixed; - right: 0; /* 화면의 오른쪽 끝에 고정 */ - top: 60%; /* 화면의 상단으로부터 50%의 위치에 고정 */ - transform: translateY( - -50% - ); /* 상단 50% 위치에서 자기 자신의 높이의 절반을 위로 이동 */ + right: 0; + top: 60%; + transform: translateY(-50%); width: 20%; border: 1px solid black; padding: 20px; @@ -45,3 +43,15 @@ flex-direction: column; align-items: center; } +@media screen and (max-width: 768px) { + .possible { + position: static; + width: 40%; + margin: 200px 20px; + } + + .flex-row { + flex-direction: column; + width: 100%; + } +} diff --git a/react-whenMeet/src/styles/ResultMake.css b/react-whenMeet/src/styles/ResultMake.css index e678d31596e9b64343b57eade4f32b55fe151783..482917bc63f5ac5ec045244ab796af8d9b21736f 100644 --- a/react-whenMeet/src/styles/ResultMake.css +++ b/react-whenMeet/src/styles/ResultMake.css @@ -18,15 +18,60 @@ box-shadow: 0px 1px 5px rgba(95, 189, 255, 0.5); /* 구분선에 그림자 효과 추가 */ padding-top: 20px; margin-top: 30px; + width: 50%; + overflow-x: auto; +} + +.mostTime, +.possible { + border: 1px solid #ddd; + padding: 20px; + margin: 10px; /* 각 요소 주변의 공간을 더욱 명확히 */ + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* 세련된 그림자 효과 */ + border-radius: 8px; /* 모서리를 둥글게 */ + background-color: #ffffff; /* 배경색 설정 */ + box-sizing: border-box; /* 패딩과 보더를 너비에 포함 */ } .mostTime { position: fixed; right: 0; top: 30%; - transform: translateY(-50%); + transform: translateY(-60%); + width: 20%; + border: 1px solid black; + padding: 20px; + margin-right: 2%; +} +.possible { + position: fixed; + right: 0; + top: 60%; + transform: translateY(-40%); width: 20%; border: 1px solid black; padding: 20px; margin-right: 2%; } + +@media screen and (max-width: 768px) { + .flex-bottom-container { + display: flex; + justify-content: space-between; + width: 100%; + + bottom: 0; + left: 0; + } + + .mostTime, + .possible { + position: static; + width: 40%; + margin: 200px 20px; + } + .flex-row { + flex-direction: column; + width: 100%; + } +}