diff --git a/src/components/frontWorkList/CommentBox.jsx b/src/components/frontWorkList/CommentBox.jsx new file mode 100644 index 0000000..651ffbd --- /dev/null +++ b/src/components/frontWorkList/CommentBox.jsx @@ -0,0 +1,153 @@ +import { useState } from "react"; +import styled from "styled-components"; +import useModal from "../../hooks/useModal"; + +const CommentBox = ({commentData}) => { + const [commentText, setCommentText] = useState(''); + const [charCount, setCharCount] = useState(0); + // useModal 사용해서 글자수 제한 모달 구현 + const { isModalOpen, openModal, closeModal, Modal } = useModal(); + + // 댓글 갯수 + const commentCount = commentData.length; + + // open modal + const handleModal = (e) => { + const text = e.target.value; + if (text.length <= 1000 && !isModalOpen) { + setCommentText(text); + setCharCount(text.length); + } else { + setCommentText(text.slice(0,1000)); // 글자수 제한 초과 부분을 제외하고 저장 + setCharCount(10); + openModal(); + } + }; + + // close modal + const handelModalConfirm = () => { + closeModal(); + } + + return ( + + 댓글 {commentCount} + + + + {charCount} / 1000 + + 등록 + + {/* modal */} + + +
+ 제한된 글자수를 초과했습니다. +
+ +
+
+
+ + ); +}; + +const CommentWrapper = styled.div` + padding: 16px; + margin: 0 auto 40px; + background-color: #3F424E; + flex-direction: column; + align-items: flex-start; +`; + +const CommentCount = styled.div` + margin-bottom: 11px; + front-size: 15px; +`; + +const CommentContainer = styled.div` + display: flex; + justify-content: space-between; +`; + +const TextAreaContainer = styled.div` + display: flex; + flex-direction: column; + width: 100%; +`; + +const CommentTextArea = styled.textarea` + padding: 8px; + resize: none; + background-color: #A7A7A7; + color: black; + font-size: 13.5px; + border: 1px solid #A7A7A7; + height: 57px; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + overflow: hidden; + outline: none; + border: none; +`; + +const CharCount = styled.div` + text-align: right; + color: #393939; + padding: 0 15px; + background-color: #A7A7A7; + border-bottom-right-radius: 6px; + border-bottom-left-radius: 6px;; +`; + +const SubmitButton = styled.button` + background-color: #007bff; + font-size: 15px; + color: black; + border: none; + padding: 10px 16px; + cursor: pointer; + background-color: #A7A7A7; + margin-left: 11px; + align-self: flex-end; + border-radius: 6px; + width: 91px; + height: 91px; +`; + +const ModalContent = styled.div` + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; + height: 100%; + margin-top: 60px; + font-size: 14px; + text-align: center; +`; + +const ModalBody = styled.div` + margin-bottom: 20px; + color: #c7c7c7; + text-align: center; + margin-bottom: 30px; +`; + +const Button = styled.button` + width: 301px; + height: 37px; + background-color: #5263ff; + font-size: 14px; + color: #ffffff; + border: none; + border-radius: 6px; + margin-top: 20px; +`; + +export default CommentBox; \ No newline at end of file diff --git a/src/components/frontWorkList/CommentItem.jsx b/src/components/frontWorkList/CommentItem.jsx new file mode 100644 index 0000000..248cd57 --- /dev/null +++ b/src/components/frontWorkList/CommentItem.jsx @@ -0,0 +1,64 @@ +import styled from "styled-components"; +import UserIcon from "../../assets/people.png"; +import LikeIcon from "../../assets/heart.png"; + +const CommentItem = ({ item }) => { + return ( + + + {item.user} + + {item.content} + + {item.date} + + {item.like} + + + + ); +}; + +const Wrapper = styled.div` + background-color: transparent; + border-color: #868686 transparent; + border-style: solid; + border-width: 1px 0; + padding: 17px; + + &:not(:first-child) { + margin-top: -1px; // 첫 번째 div는 마진을 설정 안함 + } +`; + +const Name = styled.div` + display: flex; + align-items: center; +`; + +const Content = styled.div` + padding: 14px 24px 7px; +`; + +const Date = styled.div` + padding: 7px 24px; +`; + +const Like = styled.div` + display: flex; + align-items: center; + background-color: #3F424E; + border-radius: 6px; + padding: 8px; +`; + +const Icon = styled.img` + margin-right: 8px; +`; + +const Container = styled.div` + display: flex; + justify-content: space-between; +`; + +export default CommentItem; \ No newline at end of file diff --git a/src/components/frontWorkList/CommentList.jsx b/src/components/frontWorkList/CommentList.jsx new file mode 100644 index 0000000..e76ba45 --- /dev/null +++ b/src/components/frontWorkList/CommentList.jsx @@ -0,0 +1,125 @@ +import { useState, useEffect } from "react"; +import styled from "styled-components"; +import CommentItem from "./CommentItem"; +import CommentBox from "./CommentBox"; + +function isEqual(arr1, arr2) { + if (arr1.length !== arr2.length) { + return false; + } + + for (let i = 0; i < arr1.length; i++) { + if (arr1[i].id !== arr2[i].id) { + return false; + } + } + + return true; +} + +export default function CommentList() { + // 임시 댓글 data + const commentData = [ + {id: 0, user: "소밍밍", content: "안녕하세요. 문제가 너무 좋네요. 덕분에 학습 잘 하고 갑니다.", date: "2023.08.20 21:23", like: 7}, + {id: 1, user: "소밍밍", content: "안녕하세요. 문제가 너무 좋네요. 덕분에 학습 잘 하고 갑니다.", date: "2023.08.23 21:23", like: 5}, + {id: 2, user: "소밍밍", content: "안녕하세요. 문제가 너무 좋네요. 덕분에 학습 잘 하고 갑니다.", date: "2023.08.10 21:23", like: 0}, + {id: 3, user: "소밍밍", content: "안녕하세요. 문제가 너무 좋네요. 덕분에 학습 잘 하고 갑니다.", date: "2023.08.09 21:23", like: 5}, + ]; + + const [data, setData] = useState(commentData); + const [showMore, setShowMore] = useState(false); // 답안 댓글 토글 + const [sortOption, setSortOption] = useState('latest'); // 초기 정렬 기준 : 최신순 + + useEffect(() => { + // 댓글 정렬 + const sortData = (sortKey) => { + const sortedData = [...data]; // 원본 데이터를 변경하지 않기 위해 복사 + + if (sortKey === 'latest') { + sortedData.sort((a, b) => new Date(b.date) - new Date(a.date)); // 최신순 + } else if (sortKey === 'oldest') { + sortedData.sort((a, b) => new Date(a.date) - new Date(b.date)); // 등록순 + } else if (sortKey === 'mostLiked') { + sortedData.sort((a, b) => b.like - a.like); // 추천순 + } + + return sortedData; + }; + const sortedData = sortData(sortOption); + + // 정렬된 데이터와 현재 데이터가 다를 경우에만 업데이트 + if (!isEqual(sortedData, data)) { + setData(sortedData); + } + }, [sortOption, data]); + + const visibleBtn= data.length > 3; // 댓글이 3개 이상일 때만 버튼이 보임 + const toggleShowMore = () => { + setShowMore(!showMore); + } + + return( +
+ + + + setSortOption('latest')} + active={sortOption === 'latest'} + > + 최신순 + + setSortOption('oldest')} + active={sortOption === 'oldest'} + > + 등록순 + + setSortOption('mostLiked')} + active={sortOption === 'mostLiked'} + > + 추천순 + + + + {data.slice(0, showMore ? data.length : 3).map((comment, index) => ( + + ))} + {visibleBtn && ( + + {showMore ? "댓글 숨기기" : "댓글 더보기"} + + )} +
+ ); +}; + +const SortOptions = styled.div` + align-items: center; + display: flex; + padding: 5px 0; + margin-bottom: 11px; +`; + +const SortButton = styled.button` + margin-right: 15spx; + background-color: transparent; + color: ${(props) => (props.active ? '#FFFFFF' : '#838383')}; + font-size: 16px; + border: none; + cursor: pointer; +`; + +const ShowMoreBtn = styled.button` + width: 1090px; + height: 37px; + border: none; + border-radius: 6px; + background-color: #3f424e; + color: #ffffff; + font-size: 14px; + font-weight: 500; + margin-bottom: 35px; + margin-top: 20px; +`; \ No newline at end of file diff --git a/src/components/frontWorkList/FrontWorkItem.jsx b/src/components/frontWorkList/FrontWorkItem.jsx new file mode 100644 index 0000000..0d8158e --- /dev/null +++ b/src/components/frontWorkList/FrontWorkItem.jsx @@ -0,0 +1,55 @@ +import styled from "styled-components"; +import { Link } from 'react-router-dom'; +import ViewIcon from "../../assets/see.png"; +import StartBtn from "../../assets/Btn_start.png"; + +function formatNumber(num) { + return num < 10 ? `0${num}` : num.toString(); +} + +const FrontWorkItem = ({ item, index}) => { + return ( + +
{formatNumber(index+1)}
+
{item.title}
+
{item.date}
+
+ + {formatNumber(item.view)} + +
+
+ + 학습하기 + +
+
+ ); +}; + +const Container = styled.div` + display: flex; + position: relative; + justify-content: space-between; + align-items: center; + width: 1090px; + font-size: 15px; + padding: 5px 0; + border-bottom: 1px solid #ddd; + border-top: 1px solid #ddd; + + &:not(:first-child) { + margin-top: -1px; // 첫 번째 div는 마진을 설정 안함 + } +`; + +const IconTextWrapper = styled.div` + display: flex; + align-items: center; +`; + +const Icon = styled.img` + padding: 0 5px; +`; + +export default FrontWorkItem; \ No newline at end of file diff --git a/src/components/frontWorkList/WorkList.jsx b/src/components/frontWorkList/WorkList.jsx new file mode 100644 index 0000000..59c646c --- /dev/null +++ b/src/components/frontWorkList/WorkList.jsx @@ -0,0 +1,186 @@ +import { useState, useEffect } from "react"; +import styled from "styled-components"; +import FrontItem from "./FrontWorkItem"; +import SearchBar from "../front/SearchBar"; +import usePagination from "../../hooks/usePagination"; +import LeftIcon from "../../assets/left.png"; +import RightIcon from "../../assets/right.png"; + +function isEqual(arr1, arr2) { + if (arr1.length !== arr2.length) { + return false; + } + + for (let i = 0; i < arr1.length; i++) { + if (arr1[i].id !== arr2[i].id) { + return false; + } + } + + return true; +} + +export default function WorkList() { + // 임시 프론트 문제집 data + const frontWorkData = [ + {id: 0, title: "정보처리기사실기_2023정보처리기사실기_2023", date: "2023-10-16", view: 10}, + {id: 1, title: "정보처리기사실기_2023정보처리기사실기_2023", date: "2023-10-15", view: 10}, + {id: 2, title: "정보처리기사실기_2023정보처리기사실기_2023", date: "2023-10-14", view: 2}, + {id: 3, title: "정보처리기사실기_2023정보처리기사실기_2023", date: "2023-10-13", view: 0}, + {id: 4, title: "정보처리기사실기_2023정보처리기사실기_2023", date: "2023-10-12", view: 1}, + {id: 5, title: "정보처리기사실기_2023정보처리기사실기_2023", date: "2023-10-11", view: 15}, + {id: 6, title: "정보처리기사실기_2023정보처리기사실기_2023", date: "2023-10-10", view: 12}, + {id: 7, title: "정보처리기사실기_2023정보처리기사실기_2023", date: "2023-10-09", view: 11}, + {id: 8, title: "정보처리기사실기_2023정보처리기사실기_2023", date: "2023-10-08", view: 9}, + {id: 9, title: "정보처리기사실기_2023정보처리기사실기_2023", date: "2023-10-07", view: 19}, + {id: 10, title: "정보처리기사실기_2023정보처리기사실기_2023", date: "2023-10-06", view: 30}, + {id: 11, title: "정보처리기사실기_2023정보처리기사실기_2023", date: "2023-10-05", view: 5}, + {id: 12, title: "정보처리기사실기_2023정보처리기사실기_2023", date: "2023-10-04", view: 6}, + {id: 13, title: "정보처리기사실기_2023정보처리기사실기_2023", date: "2023-10-03", view: 0}, + {id: 14, title: "정보처리기사실기_2023정보처리기사실기_2023", date: "2023-10-02", view: 11}, + {id: 15, title: "정보처리기사실기_2023정보처리기사실기_2023", date: "2023-10-01", view: 10}, + {id: 16, title: "정보처리기사실기_2023정보처리기사실기_2023", date: "2023-09-30", view: 10}, + {id: 17, title: "정보처리기사실기_2023정보처리기사실기_2023", date: "2023-09-29", view: 10}, + {id: 18, title: "정보처리기사실기_2023정보처리기사실기_2023", date: "2023-09-28", view: 10}, + {id: 19, title: "정보처리기사실기_2023정보처리기사실기_2023", date: "2023-09-27", view: 10}, + ]; + const [sortOption, setSortOption] = useState('latest'); // 초기 정렬 기준 : 최신순 + const [data, setData] = useState(frontWorkData); // 데이터 상태 정의 + + const itemsPerPage = 15; // 페이지 당 보여줄 아이템 수 + const { + currentPage, + currentItems, + totalPages, + paginate, + goToPrevPage, + goToNextPage, + } = usePagination( + data, + itemsPerPage + ); + + useEffect(() => { + // 문제집 리스트 정렬 + const sortData = (sortKey) => { + const sortedData = [...data]; // 원본 데이터를 변경하지 않기 위해 복사 + + if (sortKey === 'latest') { + sortedData.sort((a, b) => new Date(b.date) - new Date(a.date)); // 최신순 + } else if (sortKey === 'oldest') { + sortedData.sort((a, b) => new Date(a.date) - new Date(b.date)); // 등록순 + } else if (sortKey === 'mostViewed') { + sortedData.sort((a, b) => b.view - a.view); // 조회순 + } + + return sortedData; + }; + const sortedData = sortData(sortOption); + + // 정렬된 데이터와 현재 데이터가 다를 경우에만 업데이트 + if (!isEqual(sortedData, data)) { + setData(sortedData); + } + }, [sortOption, data]); + + return( +
+ + + setSortOption('latest')} + active={sortOption === 'latest'} + > + 최신순 + + setSortOption('oldest')} + active={sortOption === 'oldest'} + > + 등록순 + + setSortOption('mostViewed')} + active={sortOption === 'mostViewed'} + > + 조회순 + + + + + + + {/* 필터링된 스터디 리스트 표시 */} +
+ {/* 현재 페이지의 아이템 렌더링 */} + {currentItems.map((front, index) => ( + + ))} + + {/* 페이지네이션 컴포넌트 렌더링 */} + + + left + + {Array.from({ length: totalPages }).map((_, index) => ( + paginate(index + 1)} + isSelected={index + 1 === currentPage} + > + {index + 1} + + ))} + + right + + +
+
+ ); +}; + +const Container = styled.div` + display: flex; + justify-content: space-between; + align-items: center; +`; + +const SortOptions = styled.div` + align-items: center; + display: flex; + padding: 5px 0; +`; + +const SortButton = styled.button` + margin-right: 15spx; + background-color: transparent; + color: ${(props) => (props.active ? '#FFFFFF' : '#838383')}; + font-size: 16px; + border: none; + cursor: pointer; +`; + +const PaginationContainer = styled.div` + display: flex; + align-items: center; + margin-top: 20px; + justify-content: center; + margin-bottom: 50px; +`; + +const PaginationButton = styled.button` + background-color: transparent; + border: none; + font-size: 16px; + cursor: pointer; + margin: 0 5px; +`; + +const PageButton = styled.button` + border: none; + margin: 10px; + background-color: transparent; + color: ${(props) => (props.isSelected ? "#5263ff" : "#838383")}; + font-size: 15px; +`; \ No newline at end of file diff --git a/src/pages/FrontWorkList.jsx b/src/pages/FrontWorkList.jsx index f6e8321..f7c1739 100644 --- a/src/pages/FrontWorkList.jsx +++ b/src/pages/FrontWorkList.jsx @@ -1,5 +1,112 @@ +import styled from "styled-components"; +import { Link } from 'react-router-dom'; +import { useState } from "react"; +import LikeIcon from "../assets/heart.png"; +import CmtIcon from "../assets/chat.png"; +import UserIcon from "../assets/people.png"; +import LikeBtn from "../assets/Btn_like.png"; +import AllBtn from "../assets/Btn_all.png"; +import WorkList from "../components/frontWorkList/WorkList"; +import CommentList from "../components/frontWorkList/CommentList"; + const FrontWorkList = () => { - return
프론트엔드 스터디방 메인 페이지입니다
; + const [textValue, setTextValue] = useState(0); // 추천수 올리기 + + const increaseLike = () => { + setTextValue(textValue + 1); + }; + + return ( + + + + 정보처리기사 실기_2023 총 91문제 + + + + + + 소밍밍 + + + + {textValue} + + + 04 + + + + + + 전체학습하기 + + 추천하기 + + + + + + + + + ); }; -export default FrontWorkList; +const Wrapper = styled.div` + max-width: 1090px; + margin: 0 auto 50px; +`; + +const TopSection = styled.div` + padding: 20px 0; + margin-bottom: 50px; +`; + +const TopTitle = styled.div` + font-size: 20px; + font-weight: bold; + display: flex; + align-items: center; + margin-bottom: 17px; +`; + +const Works = styled.div` + font-size: 15px; + margin-left: 42px; +`; + +const TopContent = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + font-size: 16px; +`; + +const WorkbookInfo = styled.div` + display: flex; + gap: 52px; +`; + +const LikeCmt = styled.div` + gap: 17px; + display: flex; +`; + +const IconTextWrapper = styled.div` + display: flex; + align-items: center; +`; + +const Icon = styled.img` + margin-right: 5px; +`; + +const Buttons = styled.div` + display: flex; + gap: 17px; + text-align: right; + align-items: center; +`; + +export default FrontWorkList; \ No newline at end of file