diff --git a/react-whenMeet/package-lock.json b/react-whenMeet/package-lock.json index 82f2e09d5aab83e0838708b84455a76184e87ba8..91f9c5724d2a4f8fe842b2de801da29d1cfee906 100644 --- a/react-whenMeet/package-lock.json +++ b/react-whenMeet/package-lock.json @@ -18,6 +18,7 @@ "react": "^18.2.0", "react-calendar": "^4.6.1", "react-dom": "^18.2.0", + "react-modal": "^3.16.1", "react-router-dom": "^6.18.0", "react-scripts": "^5.0.1", "web-vitals": "^2.1.4" @@ -8115,6 +8116,11 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/exenv": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", + "integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==" + }, "node_modules/exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", @@ -14949,6 +14955,29 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "node_modules/react-modal": { + "version": "3.16.1", + "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.16.1.tgz", + "integrity": "sha512-VStHgI3BVcGo7OXczvnJN7yT2TWHJPDXZWyI/a0ssFNhGZWsPmB8cF0z33ewDXq4VfYMO1vXgiv/g8Nj9NDyWg==", + "dependencies": { + "exenv": "^1.2.0", + "prop-types": "^15.7.2", + "react-lifecycles-compat": "^3.0.0", + "warning": "^4.0.3" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18", + "react-dom": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18" + } + }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -17254,6 +17283,14 @@ "makeerror": "1.0.12" } }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", diff --git a/react-whenMeet/package.json b/react-whenMeet/package.json index 3f6713368775864ab267991301fd04aa36e33f6d..dafb6ea7b18bff19ba9b489f9521e02e3c51cfff 100644 --- a/react-whenMeet/package.json +++ b/react-whenMeet/package.json @@ -13,6 +13,7 @@ "react": "^18.2.0", "react-calendar": "^4.6.1", "react-dom": "^18.2.0", + "react-modal": "^3.16.1", "react-router-dom": "^6.18.0", "react-scripts": "^5.0.1", "web-vitals": "^2.1.4" diff --git a/react-whenMeet/src/components/PasswordModal.jsx b/react-whenMeet/src/components/PasswordModal.jsx new file mode 100644 index 0000000000000000000000000000000000000000..c9870adc6359584a5783ee0bd9072596eb8043c5 --- /dev/null +++ b/react-whenMeet/src/components/PasswordModal.jsx @@ -0,0 +1,46 @@ +import React, { useState } from "react"; +import Modal from "react-modal"; + +Modal.setAppElement("#root"); + +function PasswordModal({ isOpen, onRequestClose, onSubmit }) { + const [password, setPassword] = useState(""); + + const handleSubmit = () => { + onSubmit(password); + setPassword(""); + }; + const customStyles = { + content: { + width: "400px", // 모달의 너비 + height: "200px", // 모달의 높이 + top: "50%", + left: "50%", + right: "auto", + bottom: "auto", + marginRight: "-50%", + transform: "translate(-50%, -50%)", + }, + }; + + return ( + <Modal + isOpen={isOpen} + onRequestClose={onRequestClose} + contentLabel="비밀번호 입력" + style={customStyles} + > + <h2>관리자 비밀번호를 입력하세요</h2> + <input + type="password" + value={password} + onChange={(e) => setPassword(e.target.value)} + placeholder="비밀번호" + /> + <button onClick={handleSubmit}>확인</button> + <button onClick={onRequestClose}>취소</button> + </Modal> + ); +} + +export default PasswordModal; diff --git a/react-whenMeet/src/components/ResultMakeForm.js b/react-whenMeet/src/components/ResultMakeForm.js index d0d8cc7442e8b65fc2af53322425261a2a0bb83b..9c9dcc4306386109c8b283cbf95f8b7f3590cad1 100644 --- a/react-whenMeet/src/components/ResultMakeForm.js +++ b/react-whenMeet/src/components/ResultMakeForm.js @@ -1,28 +1,256 @@ +// import React, { useState, useEffect } from "react"; +// import CalendarWeek from "./CalendarWeek"; +// import { useNavigate, useParams } from "react-router-dom"; +// import axios from "axios"; + +// import "../styles/ResultMake.css"; +// function ResultMakeForm() { +// const [meetingData, setMeetingData] = useState(null); +// const { meeting_id } = useParams(); +// const { currentParticipants, maxParticipants } = meetingData || {}; +// const navigate = useNavigate(); +// const [timeLeft, setTimeLeft] = useState(""); +// const [hoveredInfo, setHoveredInfo] = useState(null); +// const [isLoading, setIsLoading] = useState(true); // 로딩 상태 관리 +// const [isModalOpen, setIsModalOpen] = useState(false); + +// useEffect(() => { +// // 서버에서 미팅 데이터를 가져오는 함수 +// const fetchMeetingData = async () => { +// setIsLoading(true); +// try { +// const response = await axios.get( +// `http://localhost:3000/meetings/${meeting_id}/details`, +// { +// withCredentials: true, +// } +// ); +// setMeetingData(response.data); +// setIsLoading(false); +// } catch (error) { +// console.error("데이터 로딩 에러:", error); +// setIsLoading(false); +// } +// }; + +// fetchMeetingData(); +// }, [meeting_id]); + +// // 타이머를 시작하고 관리하는 로직 +// useEffect(() => { +// const calculateTimeLeft = () => { +// if (!meetingData || !meetingData.voteExpiresAt) { +// // meetingData나 voteExpiresAt이 없는 경우 기본값을 반환 +// return { +// days: 0, +// hours: 0, +// minutes: 0, +// seconds: 0, +// }; +// } +// const now = new Date(); +// const voteExpires = new Date(meetingData.voteExpiresAt); +// const difference = voteExpires - now; + +// if (difference > 0) { +// return { +// 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), +// }; +// } else { +// // 시간이 이미 지난 경우 기본값을 반환 +// return { +// days: 0, +// hours: 0, +// minutes: 0, +// seconds: 0, +// }; +// } +// }; + +// const updateTimer = () => { +// const newTimeLeft = calculateTimeLeft() || {}; // newTimeLeft가 undefined인 경우를 대비해 기본 객체 할당 +// 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]); + +// const handleEdit = async () => { +// try { +// const response = await axios.get( +// `http://localhost:3000/meetings/${meeting_id}/` +// ); +// const { +// startDate, +// endDate, +// availableVotingStartTime, +// availableVotingEndTime, +// } = response.data; + +// try { +// const scheduleResponse = await axios.get( +// `http://localhost:3000/meetings/${meeting_id}/my/schedules` +// ); +// navigate("/UserTimeInfo", { +// state: { +// id: meeting_id, +// startTime: availableVotingStartTime, +// endTime: availableVotingEndTime, +// startDate, +// endDate, +// schedules: scheduleResponse.data.schedules, +// }, +// }); +// } catch (error) { +// console.error(error); +// } +// } catch (error) { +// console.error(error); +// } +// }; + +// const closeMeeting = async () => { +// const adminPassword = prompt("관리자 비밀번호를 입력하세요:"); + +// if (adminPassword) { +// try { +// await axios.post(`http://localhost:3000/meetings/${meeting_id}/close`, { +// adminPassword, +// }); + +// navigate(`/resultend/${meeting_id}`); +// } catch (error) { +// if (error.response && error.response.status === 401) { +// alert("비밀번호가 틀렸습니다."); +// } else { +// console.error("오류 발생:", error); +// } +// } +// } +// }; + +// const handleModalClose = () => { +// setIsModalOpen(false); +// }; + +// const handlePasswordConfirm = async (password) => { +// setIsModalOpen(false); +// try { +// await axios.post(`http://localhost:3000/meetings/${meeting_id}/close`, { +// adminPassword: password, +// }); +// navigate(`/resultend/${meeting_id}`); +// } catch (error) { +// if (error.response && error.response.status === 401) { +// alert("비밀번호가 틀렸습니다."); +// } else { +// console.error("오류 발생:", error); +// } +// } +// }; + +// return ( +// <> +// {isLoading ? ( +// <div>Loading...</div> +// ) : ( +// <div className="column-container"> +// <h1 className="title-box">{meetingData?.title}</h1> + +// <div> +// 현재 완료한 인원수 {currentParticipants} +// {maxParticipants != null && ` / ${maxParticipants}`} +// </div> + +// <div>종료까지 남은 시간 {timeLeft}</div> +// <button onClick={handleEdit}>수정하기</button> +// <button onClick={closeMeeting}>투표 종료하기</button> +// </div> +// )} +// {meetingData && ( +// <span className="flex-row"> +// <div className="calander-container"> +// <CalendarWeek +// participants={meetingData.participants} +// startDate={meetingData.startDate} +// endDate={meetingData.endDate} +// currentParticipants={meetingData.currentParticipants} +// maxParticipants={meetingData.maxParticipants} +// hoveredInfo={hoveredInfo} +// setHoveredInfo={setHoveredInfo} +// availableVotingStartTime={meetingData.availableVotingStartTime} +// availableVotingEndTime={meetingData.availableVotingEndTime} +// /> +// </div> + +// <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> +// </span> +// )} +// </> +// ); +// } +// export default ResultMakeForm; import React, { useState, useEffect } from "react"; import CalendarWeek from "./CalendarWeek"; +import PasswordModal from "./PasswordModal"; import { useNavigate, useParams } from "react-router-dom"; import axios from "axios"; import "../styles/ResultMake.css"; + function ResultMakeForm() { const [meetingData, setMeetingData] = useState(null); const { meeting_id } = useParams(); - const { currentParticipants, maxParticipants } = meetingData || {}; const navigate = useNavigate(); const [timeLeft, setTimeLeft] = useState(""); const [hoveredInfo, setHoveredInfo] = useState(null); - const [isLoading, setIsLoading] = useState(true); // 로딩 상태 관리 + const [isLoading, setIsLoading] = useState(true); + const [isModalOpen, setIsModalOpen] = useState(false); useEffect(() => { - // 서버에서 미팅 데이터를 가져오는 함수 const fetchMeetingData = async () => { setIsLoading(true); try { const response = await axios.get( `http://localhost:3000/meetings/${meeting_id}/details`, - { - withCredentials: true, - } + { withCredentials: true } ); setMeetingData(response.data); setIsLoading(false); @@ -35,17 +263,10 @@ function ResultMakeForm() { fetchMeetingData(); }, [meeting_id]); - // 타이머를 시작하고 관리하는 로직 useEffect(() => { const calculateTimeLeft = () => { if (!meetingData || !meetingData.voteExpiresAt) { - // meetingData나 voteExpiresAt이 없는 경우 기본값을 반환 - return { - days: 0, - hours: 0, - minutes: 0, - seconds: 0, - }; + return { days: 0, hours: 0, minutes: 0, seconds: 0 }; } const now = new Date(); const voteExpires = new Date(meetingData.voteExpiresAt); @@ -59,18 +280,12 @@ function ResultMakeForm() { seconds: Math.floor((difference / 1000) % 60), }; } else { - // 시간이 이미 지난 경우 기본값을 반환 - return { - days: 0, - hours: 0, - minutes: 0, - seconds: 0, - }; + return { days: 0, hours: 0, minutes: 0, seconds: 0 }; } }; const updateTimer = () => { - const newTimeLeft = calculateTimeLeft() || {}; // newTimeLeft가 undefined인 경우를 대비해 기본 객체 할당 + const newTimeLeft = calculateTimeLeft(); const formattedTime = `${ newTimeLeft.days ? newTimeLeft.days + "일 " : "" }${newTimeLeft.hours ? newTimeLeft.hours + "시간 " : ""}${ @@ -79,14 +294,65 @@ function ResultMakeForm() { setTimeLeft(formattedTime); }; - updateTimer(); const timerId = setInterval(updateTimer, 1000); - return () => clearInterval(timerId); }, [meetingData]); + const handleEdit = async () => { + try { + const response = await axios.get( + `http://localhost:3000/meetings/${meeting_id}/` + ); + const { + startDate, + endDate, + availableVotingStartTime, + availableVotingEndTime, + } = response.data; + + try { + const scheduleResponse = await axios.get( + `http://localhost:3000/meetings/${meeting_id}/my/schedules` + ); + navigate("/UserTimeInfo", { + state: { + id: meeting_id, + startTime: availableVotingStartTime, + endTime: availableVotingEndTime, + startDate, + endDate, + schedules: scheduleResponse.data.schedules, + }, + }); + } catch (error) { + console.error(error); + } + } catch (error) { + console.error(error); + } + }; + + const closeMeeting = () => { + setIsModalOpen(true); + }; - const handleEdit = () => { - navigate("/meetinginfo/linkpage"); + const handleModalClose = () => { + setIsModalOpen(false); + }; + + const handlePasswordSubmit = async (password) => { + setIsModalOpen(false); + try { + await axios.post(`http://localhost:3000/meetings/${meeting_id}/close`, { + adminPassword: password, + }); + navigate(`/resultend/${meeting_id}`); + } catch (error) { + if (error.response && error.response.status === 401) { + alert("비밀번호가 틀렸습니다."); + } else { + console.error("오류 발생:", error); + } + } }; return ( @@ -96,65 +362,64 @@ function ResultMakeForm() { ) : ( <div className="column-container"> <h1 className="title-box">{meetingData?.title}</h1> - <div> - 현재 완료한 인원수 {currentParticipants} - {maxParticipants != null && ` / ${maxParticipants}`} + 현재 완료한 인원수 {meetingData?.currentParticipants} /{" "} + {meetingData?.maxParticipants} </div> - <div>종료까지 남은 시간 {timeLeft}</div> <button onClick={handleEdit}>수정하기</button> - <button onClick={() => navigate(`/resultend/${meeting_id}`)}> - 투표 종료하기 - </button> + <button onClick={closeMeeting}>투표 종료하기</button> </div> )} {meetingData && ( - <span className="flex-row"> - <div className="calander-container"> - <CalendarWeek - participants={meetingData.participants} - startDate={meetingData.startDate} - endDate={meetingData.endDate} - currentParticipants={meetingData.currentParticipants} - maxParticipants={meetingData.maxParticipants} - hoveredInfo={hoveredInfo} - setHoveredInfo={setHoveredInfo} - availableVotingStartTime={meetingData.availableVotingStartTime} - availableVotingEndTime={meetingData.availableVotingEndTime} - /> + <div className="flex-row"> + <CalendarWeek + participants={meetingData.participants} + startDate={meetingData.startDate} + endDate={meetingData.endDate} + currentParticipants={meetingData.currentParticipants} + maxParticipants={meetingData.maxParticipants} + hoveredInfo={hoveredInfo} + setHoveredInfo={setHoveredInfo} + availableVotingStartTime={meetingData.availableVotingStartTime} + availableVotingEndTime={meetingData.availableVotingEndTime} + /> + </div> + )} + <PasswordModal + isOpen={isModalOpen} + 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> + )} - <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> - </span> - )} + {hoveredInfo && ( + <div style={{ textAlign: "center" }}> + <strong> + {hoveredInfo.date} {hoveredInfo.time}에 가능한 사람: + </strong> + <ul> + {hoveredInfo.participants.map((name) => ( + <li key={name}>{name}</li> + ))} + </ul> + </div> + )} + </span> </> ); } + export default ResultMakeForm; diff --git a/react-whenMeet/src/styles/CalendarWeek.css b/react-whenMeet/src/styles/CalendarWeek.css index 363c602f617b9b2fc00455b31be697fc9f1b7a23..5b4758c4c9e70bf5d73de20a026fad68deb5c839 100644 --- a/react-whenMeet/src/styles/CalendarWeek.css +++ b/react-whenMeet/src/styles/CalendarWeek.css @@ -19,6 +19,7 @@ table { width: 100%; border-collapse: collapse; text-align: center; + margin-bottom: 4%; } th, diff --git a/react-whenMeet/src/styles/ResultEnd.css b/react-whenMeet/src/styles/ResultEnd.css index b1d979b6453ac5b72add91645ffb626c7d634ea2..8843a04a112bf5bc63572a23fac8f091bbf70054 100644 --- a/react-whenMeet/src/styles/ResultEnd.css +++ b/react-whenMeet/src/styles/ResultEnd.css @@ -1,6 +1,5 @@ .title-box { width: 30%; - margin: 0 auto; text-align: center; } .form-container { @@ -24,6 +23,7 @@ width: 20%; border: 1px solid black; padding: 20px; + margin-right: 2%; } .row { diff --git a/react-whenMeet/src/styles/ResultMake.css b/react-whenMeet/src/styles/ResultMake.css index d300a1f756267f00749e9695b8393b6ea301fa3a..84aca2f51337d39a3769f29e4782bb7b7b898520 100644 --- a/react-whenMeet/src/styles/ResultMake.css +++ b/react-whenMeet/src/styles/ResultMake.css @@ -1,12 +1,12 @@ .title-box { - width: 30%; - margin: 0 auto; + width: 80%; text-align: center; } .column-container { display: flex; flex-direction: column; + align-items: center; } .flex-row { @@ -22,4 +22,5 @@ width: 20%; border: 1px solid black; padding: 20px; + margin-right: 2%; }