From 1f36d175a2079041c0b0484fad7a9d28227a1181 Mon Sep 17 00:00:00 2001 From: chaerin <dbcofls6961@ajou.ac.kr> Date: Sat, 9 Dec 2023 16:08:08 +0900 Subject: [PATCH] fix: modify search, vote, infiniteScroll, timeInquiry --- src/components/infiniteScroll.js | 68 +++++++++---------- src/components/infiniteScroll.module.css | 2 +- src/components/{ => recruit}/comment.js | 4 +- .../{ => recruit}/comment.module.css | 0 src/components/recruit/createVote.js | 3 +- src/components/recruit/recruitDetail.js | 2 +- .../recruit/recruitDetail.module.css | 6 +- src/components/recruit/recruitList.js | 40 ++++++++--- src/components/recruit/timeInquiry.js | 16 +---- src/components/recruit/vote.js | 39 +++++++++-- src/components/recruit/vote.module.css | 15 +++- src/components/search.js | 44 +++++++----- 12 files changed, 148 insertions(+), 91 deletions(-) rename src/components/{ => recruit}/comment.js (98%) rename src/components/{ => recruit}/comment.module.css (100%) diff --git a/src/components/infiniteScroll.js b/src/components/infiniteScroll.js index d994fd9..09fb749 100644 --- a/src/components/infiniteScroll.js +++ b/src/components/infiniteScroll.js @@ -1,23 +1,33 @@ import React, { useState, useEffect } from 'react'; import {PageItem} from "./recruit/recruitList"; -//import {PageItem} from "./subscribe/subscribeList" import InfiniteScrollStyles from './infiniteScroll.module.css' import {ChannelItem} from "./channel/channelList"; + function InfiniteScroll(props){ const [items, setItems] = useState([]); const [minId, setMinId] = useState(0); const [isLoading, setIsLoading] = useState(false); const page = props.pagename; + const queryString1 = props.queryString1; + const queryString2 = props.queryString2; const initialFetchData = async ()=>{ try{ - const response = await fetch(`/api/${page}`); + const response = await fetch(`/api/${page}${queryString1}`); + console.log(`/api/${page}${queryString1}`); const jsonData = await response.json(); - setMinId(jsonData['minId']); const newData = jsonData[page]; - if (page==='recruits'){ - + setMinId(jsonData['minId']); + + if(page==='channels'){ + if(newData.length>0){ + const components=newData.map((item)=>( + <ChannelItem data={item}/> + )); + setItems(prevItem => [...prevItem, ...components]); + } + }else { if(newData.length > 0) { const components = newData.map((item) => ( <PageItem data={item}/> @@ -25,16 +35,7 @@ function InfiniteScroll(props){ setItems(prevItem => [...prevItem, ...components]); } - } - else if(page==='channels'){ - if(newData.length>0){ - const components=newData.map((item)=>( - <ChannelItem data={item}/> - )); - setItems(prevItem => [...prevItem, ...components]); - } - - } + } } catch(error){ console.log('Error during fetch:', error); @@ -43,7 +44,7 @@ function InfiniteScroll(props){ useEffect(() => { initialFetchData(); // 최초 렌더링 시에 데이터 가져오기 - }, []); + }, [page, queryString1, queryString2]); const fetchData = async ()=>{ if (isLoading || minId === 0) return; @@ -51,22 +52,14 @@ function InfiniteScroll(props){ setIsLoading(true); try{ - const response = await fetch(`/api/${page}&minId=${minId}`); + const response = await fetch(`/api/${page}?minId=${minId}${queryString2}`); + console.log(`/api/${page}?minId=${minId}${queryString2}`); const jsonData = await response.json(); const newData = jsonData[page]; setMinId(jsonData['minId']); - - if (page==='recruits'){ - if(newData.length > 0) { - const components = newData.map((item) => ( - <PageItem data={item}/> - )); - - setItems(prevItem => [...prevItem, ...components]); - } - } - else if(page==='channels'){ + + if(page==='channels'){ if(newData.length>0){ for (let i = 0; i < newData.length; i += 2) { const component = ( @@ -78,7 +71,16 @@ function InfiniteScroll(props){ setItems((prevItem) => [...prevItem, component]); } } - } + }else { + if(newData.length > 0) { + const components = newData.map((item) => ( + <PageItem data={item}/> + )); + + setItems(prevItem => [...prevItem, ...components]); + } + } + } catch(error){ console.log('Error during fetch:', error); } finally{ @@ -86,15 +88,11 @@ function InfiniteScroll(props){ } }; - useEffect(() => { - fetchData(); // 최초 렌더링 시에 데이터 가져오기 - }, [page]); - // 스크롤 이벤트 감지 const handleScroll = () => { if ( - window.innerHeight + document.documentElement.scrollTop + 100 >= - document.documentElement.offsetHeight + window.innerHeight + window.scrollY + 100 >= + document.body.offsetHeight && !isLoading ) { // 스크롤이 페이지 하단에 도달하면 데이터를 가져옴 diff --git a/src/components/infiniteScroll.module.css b/src/components/infiniteScroll.module.css index 2f6da34..1f59f35 100644 --- a/src/components/infiniteScroll.module.css +++ b/src/components/infiniteScroll.module.css @@ -1,5 +1,5 @@ .loading{ - font-size: 30px; + font-size: 20px; } /* InfiniteScroll.module.css */ diff --git a/src/components/comment.js b/src/components/recruit/comment.js similarity index 98% rename from src/components/comment.js rename to src/components/recruit/comment.js index 6374f4d..ab9efea 100644 --- a/src/components/comment.js +++ b/src/components/recruit/comment.js @@ -1,7 +1,7 @@ import React, { useState, useEffect, useContext } from "react"; import commentStyles from './comment.module.css' -import { AuthContext } from "../App"; -import DeleteComment from "./recruit/deleteComment"; +import { AuthContext } from "../../App"; +import DeleteComment from "./deleteComment"; function CommentItem({ data, setCount }){ const [isDeleteOpen, setIsDeleteOpen] = useState(false); diff --git a/src/components/comment.module.css b/src/components/recruit/comment.module.css similarity index 100% rename from src/components/comment.module.css rename to src/components/recruit/comment.module.css diff --git a/src/components/recruit/createVote.js b/src/components/recruit/createVote.js index 6bf3785..b0f5506 100644 --- a/src/components/recruit/createVote.js +++ b/src/components/recruit/createVote.js @@ -1,7 +1,7 @@ import React from 'react'; import createVoteStyles from './createVote.module.css'; -function CreateVote({ id, data, create, onClose }){ +function CreateVote({ id, data }){ const createVote = async ()=>{ try{ const response = await fetch(`/api/recruits/${id}/times/save`,{ @@ -14,7 +14,6 @@ function CreateVote({ id, data, create, onClose }){ if (response.ok){ alert('투표가 등록되었습니다.'); - create(); window.location.reload(); }else{ throw new Error('Data loading error'); diff --git a/src/components/recruit/recruitDetail.js b/src/components/recruit/recruitDetail.js index 45e8e08..3cbc64d 100644 --- a/src/components/recruit/recruitDetail.js +++ b/src/components/recruit/recruitDetail.js @@ -1,6 +1,6 @@ import React, { useState, useContext } from "react"; import recruitDetailStyles from './recruitDetail.module.css'; -import Comment from '../comment'; +import Comment from './comment'; import MyScheduleApp from '../calendar/calendar'; import { AuthContext } from '../../App'; import ModifyRecruit from "./modifyRecruit"; diff --git a/src/components/recruit/recruitDetail.module.css b/src/components/recruit/recruitDetail.module.css index 2fd9533..be8def3 100644 --- a/src/components/recruit/recruitDetail.module.css +++ b/src/components/recruit/recruitDetail.module.css @@ -79,7 +79,7 @@ height: 35px; font-weight: 600; - font-size: 30px; + font-size: 27px; line-height: 30x; color: #000000; @@ -165,7 +165,7 @@ width: 500px; height: 31px; - font-size: 20px; + font-size: 18px; line-height: 38px; color: #000000; @@ -196,7 +196,7 @@ width: 500px; height: 200px; - font-size: 24px; + font-size: 22px; line-height: 33px; color: #000000; diff --git a/src/components/recruit/recruitList.js b/src/components/recruit/recruitList.js index d2efaa4..be23047 100644 --- a/src/components/recruit/recruitList.js +++ b/src/components/recruit/recruitList.js @@ -122,7 +122,11 @@ function PageItem(props){ {isParticipants ?( <div className={recruitListStyles.voteContainer}> - <button type="button" className={recruitListStyles.vote} onClick={openVote}>투표하기</button> + {props.data.vote === 'Before' || props.data.vote === 'During' ? ( + <button type="button" className={recruitListStyles.vote} onClick={openVote}>투표하기</button> + ):( + <button type="button" className={recruitListStyles.vote} onClick={openVote}>결과확인</button> + )} <div className={recruitListStyles.voteInquiry}> <Vote isOpen={isVoteOpen} onClose={closeVote} data={props.data}/> </div> @@ -139,20 +143,40 @@ function PageItem(props){ } function RecruitList(){ - const [generatedUrl, setGeneratedUrl] = useState(''); + const [generatedUrl, setGeneratedUrl] = useState('recruits'); + const [firstQueryString, setFirstQueryString] = useState(''); + const [secondQueryString, setSecondQueryString] = useState(''); + const [forceRender, setForceRender] = useState(false); const handleUrlGeneration = (url) => { setGeneratedUrl(url); }; + const handleFirstQueryString = (url) => { + setFirstQueryString(url); + }; + + const handleSecondQueryString = (url) => { + setSecondQueryString(url); + }; + + useEffect(() => { + setForceRender((prev) => !prev); + }, [generatedUrl, firstQueryString, secondQueryString]) + return( <div className={recruitListStyles.RecruitList}> - <Search currentPage='recruits' onUrlGenerated={handleUrlGeneration}/> - {generatedUrl === '' ?( - <InfiniteScroll pagename='recruits' /> - ):( - <InfiniteScroll pagename={generatedUrl} /> - )} + <Search + currentPage='recruits' + onUrlGenerated={handleUrlGeneration} + onFirstQueryString={handleFirstQueryString} + onSecondQueryString={handleSecondQueryString} + /> + <InfiniteScroll + pagename={generatedUrl} + queryString1={firstQueryString} + queryString2={secondQueryString} + /> </div> ) } diff --git a/src/components/recruit/timeInquiry.js b/src/components/recruit/timeInquiry.js index bd575e9..5a4fcd8 100644 --- a/src/components/recruit/timeInquiry.js +++ b/src/components/recruit/timeInquiry.js @@ -38,8 +38,6 @@ function TimeItem({ item, setSelectedTimes }){ } function TimeInquiry({ isOpen, onClose, data }){ - const savedIsVote = localStorage.getItem('isVote'); - const [isVote, setIsVote] = useState(savedIsVote ? JSON.parse(savedIsVote) : false); const [inquirydata, setInquirydata] = useState([]); const [isLoading, setIsLoading] = useState(true); const [selectedTimes, setSelectedTimes] = useState([]); @@ -85,12 +83,6 @@ function TimeInquiry({ isOpen, onClose, data }){ }); }; - const handleButtonClick = () => { - if(!isVote){ - setIsVote(true); - } - }; - const timeInquiry = async (body)=>{ setIsLoading(true); @@ -138,12 +130,6 @@ function TimeInquiry({ isOpen, onClose, data }){ setIsLoading(false); } } - - useEffect(() => { - if(isVote){ - localStorage.setItem('isVote', JSON.stringify(isVote)); - } - }, [isVote]); return( <div className={timeInquiryStyles.timeInquiry}> @@ -245,7 +231,7 @@ function TimeInquiry({ isOpen, onClose, data }){ <div className={timeInquiryStyles.voteButton}> <div className={timeInquiryStyles.createVote}> - <CreateVote id={data.id} data={selectedTimes} create={handleButtonClick} onClose={onClose}/> + <CreateVote id={data.id} data={selectedTimes}/> </div> </div> diff --git a/src/components/recruit/vote.js b/src/components/recruit/vote.js index 3bbcb83..ea9ddd8 100644 --- a/src/components/recruit/vote.js +++ b/src/components/recruit/vote.js @@ -69,7 +69,7 @@ function Vote({ isOpen, onClose, data }) { } } - const vote = async()=>{ + const vote = async ()=>{ try{ const response = await fetch(`/api/recruits/${data.id}/times/vote`,{ method: 'POST', @@ -90,8 +90,24 @@ function Vote({ isOpen, onClose, data }) { } } - const closedVote = ()=>{ + const endVote = async ()=>{ + try{ + const response = await fetch(`/api/recruits/${data.id}/times/vote`,{ + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + }); + if (response.ok){ + alert("투표가 마감되었습니다."); + window.location.reload(); + }else{ + throw new Error('Data loading error'); + } + } catch(error){ + console.log('Error during fetch:', error); + } } useEffect(() => { @@ -116,10 +132,21 @@ function Vote({ isOpen, onClose, data }) { </div> <div className={voteStyles.voteContainer}> - <button className={voteStyles.goVote} onClick={vote}>투표하기</button> - {userData.nickname === data.Writer.nickname && - <button className={voteStyles.closedVote} onClick={closedVote}>투표 마감</button> - } + {data.vote === 'During' ? ( + <div> + <button className={voteStyles.goVote} onClick={vote}>투표하기</button> + {userData.nickname === data.Writer.nickname && + <button className={voteStyles.goVote} onClick={endVote}>투표 마감</button> + } + </div> + ):( + <div> + <button className={voteStyles.endVote} onClick={vote} disabled>투표하기</button> + {userData.nickname === data.Writer.nickname && + <button className={voteStyles.endVote} onClick={endVote} disabled>투표 마감</button> + } + </div> + )} </div> </div> </div> diff --git a/src/components/recruit/vote.module.css b/src/components/recruit/vote.module.css index d3adb31..beb10d3 100644 --- a/src/components/recruit/vote.module.css +++ b/src/components/recruit/vote.module.css @@ -98,7 +98,7 @@ gap: 10px; } -.goVote, .closedVote{ +.goVote{ width: 90px; height: 40px; border:none; @@ -110,3 +110,16 @@ box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2); /* 그림자 추가 */ transition: background-color 0.3s, color 0.3s, transform 0.3s; } + +.endVote{ + width: 90px; + height: 40px; + border:none; + border-radius: 5px; + cursor: pointer; + + color: white; + background-color:#b5b9c5; + box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2); /* 그림자 추가 */ + transition: background-color 0.3s, color 0.3s, transform 0.3s; +} \ No newline at end of file diff --git a/src/components/search.js b/src/components/search.js index f7d8d11..93edb78 100644 --- a/src/components/search.js +++ b/src/components/search.js @@ -7,7 +7,7 @@ import ExistingChannel from './channel/existingChannel'; -function Search({ currentPage, onUrlGenerated }){ +function Search({ currentPage, onUrlGenerated, onFirstQueryString, onSecondQueryString }){ const [isModalOpen, setIsModalOpen] = useState(false); const [searchTerm, setSearchTerm] = useState(''); const [searchType, setSearchType] = useState('title'); @@ -16,6 +16,7 @@ function Search({ currentPage, onUrlGenerated }){ const [myChannelData, setmyChannelData]=useState(''); const navigate = useNavigate(); + const openModal = () => { setIsModalOpen(true); }; @@ -59,23 +60,32 @@ function Search({ currentPage, onUrlGenerated }){ const handleSearch = () => { - let searchURL = `${currentPage}/search?`; + let searchURL = `${currentPage}/search`; + let firstQueryString = '?'; + let secondQueryString = '&'; if (searchType === 'title') { - searchURL += `title=${searchTerm}`; + firstQueryString += `title=${searchTerm}`; + secondQueryString += `title=${searchTerm}`; } else if (searchType === 'content') { - searchURL += `content=${searchTerm}`; + firstQueryString += `content=${searchTerm}`; + secondQueryString += `content=${searchTerm}`; } else if (searchType === 'writer') { - searchURL += `writer=${searchTerm}`; + firstQueryString += `writer=${searchTerm}`; + secondQueryString += `writer=${searchTerm}`; } else if (searchType === 'title&content') { - searchURL += `title=${searchTerm}&content=${searchTerm}`; + firstQueryString += `title=${searchTerm}&content=${searchTerm}`; + secondQueryString += `title=${searchTerm}&content=${searchTerm}`; } if (sortType !== ''){ - searchURL += `&sort=${sortType}`; + firstQueryString += `&sort=${sortType}`; + secondQueryString += `&sort=${sortType}`; } onUrlGenerated(searchURL); + onFirstQueryString(firstQueryString); + onSecondQueryString(secondQueryString); }; return( @@ -90,16 +100,16 @@ function Search({ currentPage, onUrlGenerated }){ onChange={(e) => setSearchTerm(e.target.value)} /> <div onClick={handleSearch}> - <svg className={searchStyles.icon} width="23" height="24" viewBox="0 0 23 24" fill="none" xmlns="http://www.w3.org/2000/svg"> - <g clipPath="url(#clip0_40_6242)"> - <path d="M14.8542 13.9808H14.0971L13.8287 13.722C14.7679 12.6295 15.3333 11.2112 15.3333 9.66825C15.3333 6.22784 12.5446 3.43909 9.10417 3.43909C5.66375 3.43909 2.875 6.22784 2.875 9.66825C2.875 13.1087 5.66375 15.8974 9.10417 15.8974C10.6471 15.8974 12.0654 15.332 13.1579 14.3928L13.4167 14.6612V15.4183L18.2083 20.2003L19.6362 18.7724L14.8542 13.9808ZM9.10417 13.9808C6.71792 13.9808 4.79167 12.0545 4.79167 9.66825C4.79167 7.282 6.71792 5.35575 9.10417 5.35575C11.4904 5.35575 13.4167 7.282 13.4167 9.66825C13.4167 12.0545 11.4904 13.9808 9.10417 13.9808Z" fill="#323232"/> - </g> - <defs> - <clipPath id="clip0_40_6242"> - <rect width="23" height="23" fill="white" transform="translate(0 0.564087)"/> - </clipPath> - </defs> - </svg> + <svg className={searchStyles.icon} width="23" height="24" viewBox="0 0 23 24" fill="none" xmlns="http://www.w3.org/2000/svg"> + <g clipPath="url(#clip0_40_6242)"> + <path d="M14.8542 13.9808H14.0971L13.8287 13.722C14.7679 12.6295 15.3333 11.2112 15.3333 9.66825C15.3333 6.22784 12.5446 3.43909 9.10417 3.43909C5.66375 3.43909 2.875 6.22784 2.875 9.66825C2.875 13.1087 5.66375 15.8974 9.10417 15.8974C10.6471 15.8974 12.0654 15.332 13.1579 14.3928L13.4167 14.6612V15.4183L18.2083 20.2003L19.6362 18.7724L14.8542 13.9808ZM9.10417 13.9808C6.71792 13.9808 4.79167 12.0545 4.79167 9.66825C4.79167 7.282 6.71792 5.35575 9.10417 5.35575C11.4904 5.35575 13.4167 7.282 13.4167 9.66825C13.4167 12.0545 11.4904 13.9808 9.10417 13.9808Z" fill="#323232"/> + </g> + <defs> + <clipPath id="clip0_40_6242"> + <rect width="23" height="23" fill="white" transform="translate(0 0.564087)"/> + </clipPath> + </defs> + </svg> </div> </div> <select -- GitLab