diff --git a/bookduck/src/assets/common/badge-alarm.svg b/bookduck/src/assets/common/badge-alarm.svg index 5844936..c9d9487 100644 --- a/bookduck/src/assets/common/badge-alarm.svg +++ b/bookduck/src/assets/common/badge-alarm.svg @@ -1,9 +1,9 @@ - - - - - - - + + + + + + + \ No newline at end of file diff --git a/bookduck/src/assets/common/waving-hand-alarm.svg b/bookduck/src/assets/common/waving-hand-alarm.svg index 9088c9d..68a224e 100644 --- a/bookduck/src/assets/common/waving-hand-alarm.svg +++ b/bookduck/src/assets/common/waving-hand-alarm.svg @@ -1,7 +1,7 @@ - - + + - + diff --git a/bookduck/src/components/MainPage/BookDisplay.jsx b/bookduck/src/components/MainPage/BookDisplay.jsx new file mode 100644 index 0000000..cecb445 --- /dev/null +++ b/bookduck/src/components/MainPage/BookDisplay.jsx @@ -0,0 +1,44 @@ +import React, { useEffect } from "react"; +import bookCard from "../../assets/mainPage/bookcard.svg"; +import music from "../../assets/mainPage/music.svg"; +const BookDisplay = ({ bookNumber }) => { + useEffect(() => { + console.log(bookNumber); + }, []); + return ( +
+
+ {/* 첫 번째 북박스 */} +
+ Book Card +
+ {/* 두 번째 북박스 */} + {bookNumber === 2 && ( +
+ Book Card +
+ )} + {/* 음악 박스 */} +
+
+ by + Music Icon +
+
+ + 노래 제목 + + + 책 제목 + +
+
+
+
+ ); +}; + +export default BookDisplay; diff --git a/bookduck/src/components/MainPage/ExtractCard.jsx b/bookduck/src/components/MainPage/ExtractCard.jsx index 17811c5..1f24157 100644 --- a/bookduck/src/components/MainPage/ExtractCard.jsx +++ b/bookduck/src/components/MainPage/ExtractCard.jsx @@ -1,6 +1,14 @@ import { useNavigate } from "react-router-dom"; -const ExtractCard = ({ content, title, author, page, onClick, selected }) => { +const ExtractCard = ({ + onClick, + selected, + content, + visibility, + pageNumber, + title, + author, +}) => { const navigate = useNavigate(); return ( @@ -10,7 +18,7 @@ const ExtractCard = ({ content, title, author, page, onClick, selected }) => { ${selected && " border-[1px] border-[#6B7FF0]"}`} >
- {page || "페이지"}p + {pageNumber || "페이지"}p
diff --git a/bookduck/src/components/MainPage/OneBookCard.jsx b/bookduck/src/components/MainPage/OneBookCard.jsx index 5f246e7..82eb592 100644 --- a/bookduck/src/components/MainPage/OneBookCard.jsx +++ b/bookduck/src/components/MainPage/OneBookCard.jsx @@ -1,4 +1,5 @@ import React, { useState, useRef, useEffect } from "react"; +import { post } from "../../api/example"; import bookCard from "../../assets/mainPage/bookcard.svg"; import music from "../../assets/mainPage/music.svg"; import infoMusicBox from "../../assets/mainPage/info-musicbox.svg"; @@ -8,31 +9,63 @@ const OneBookCard = ({ setBottomSheetShow, selected, setSelected, + bookNumber = 1, + firstImg, + secondImg, + firstId, + secondId, + readOnly, setVisible, + setEnabled = () => {}, + setCardData = () => {}, }) => { - const singerRef = useRef(null); - const memoRef = useRef(null); - + //상태 관리 + const [cardType, setCardType] = useState("BOOK_WITH_SONG"); const [singer, setSinger] = useState(""); const [song, setSong] = useState(""); - const [memo, setMemo] = useState(""); const [bookTitle, setBookTitle] = useState(""); - const [isMusic, setIsMusic] = useState(true); + const [memo, setMemo] = useState(""); + //useEffect hook useEffect(() => { - if (!readOnly && selected === "music") { - if (isMusic) { - singerRef.current?.focus(); + let enabled = false; + if (bookNumber === 1) { + if (cardType === "BOOK_WITH_SONG") { + enabled = Boolean(firstImg && singer && song); } else { - memoRef.current?.focus(); + enabled = Boolean(firstImg && memo); } } - }, [isMusic, selected]); + if (bookNumber === 2) { + if (cardType === "BOOK_WITH_SONG") { + enabled = Boolean(firstImg && secondImg && singer && song); + } else { + enabled = Boolean(firstImg && secondImg && memo); + } + } + console.log(enabled); + setEnabled(enabled); + setCardData({ + cardType: cardType, + resourceId1: firstId, + resourceId2: secondId || null, + text1: cardType === "BOOK_WITH_SONG" ? singer : memo, + text2: cardType === "BOOK_WITH_SONG" ? song : null, + text3: cardType === "BOOK_WITH_SONG" ? bookTitle : null, + }); + }, [firstImg, secondImg, singer, song, memo, cardType, bookNumber]); + useEffect(() => { + console.log(bookTitle); + }, [bookTitle]); + + //이벤트 핸들러 const handleToggle = () => { - setIsMusic((prev) => !prev); + setCardType((prev) => + prev === "BOOK_WITH_SONG" ? "BOOK_WITH_MEMO" : "BOOK_WITH_SONG" + ); }; const handleChange = (e) => { @@ -56,6 +89,7 @@ const OneBookCard = ({ setBottomSheetShow(true); } }; + const handleMusicClick = () => { if (!readOnly) { setVisible(false); @@ -84,7 +118,7 @@ const OneBookCard = ({ selected === "firstBook" ? "border-[1px] border-[#6B7FF0]" : "" } flex items-center justify-center w-[5.125rem] h-full bg-gray-custom rounded-[0.375rem] shadow-custom`} > - Book Card + Book Card
{/* 두 번째 북박스 */} @@ -103,7 +137,7 @@ const OneBookCard = ({ selected === "secondBook" ? "border-[1px] border-[#6B7FF0]" : "" } flex items-center justify-center w-[5.125rem] h-full bg-gray-custom rounded-[0.375rem] shadow-custom`} > - Book Card + Book Card
)} @@ -115,32 +149,49 @@ const OneBookCard = ({ } ${bookNumber === 1 ? "w-[16.4375rem]" : "w-[10.8125rem]"} py-3 px-4 flex flex-col justify-between bg-gray-10 shadow-custom rounded-[0.75rem]`} > - {isMusic ? ( + {cardType === "BOOK_WITH_SONG" ? ( <> -
+

- OST by{" "} + by

+ {!singer && ( +
+ + * + + 가수명 +
+ )} Music Icon
-
+
+ {!song && ( +
+ + * + + + 노래 제목 + +
+ )}
{selected === "music" && ( -
- {isMusic ? ( +
+ {cardType === "BOOK_WITH_SONG" ? ( Music Box Icon ) : ( Memo Box Icon diff --git a/bookduck/src/components/MainPage/ReadingSpaceComponent.jsx b/bookduck/src/components/MainPage/ReadingSpaceComponent.jsx index d4e0368..14b5d64 100644 --- a/bookduck/src/components/MainPage/ReadingSpaceComponent.jsx +++ b/bookduck/src/components/MainPage/ReadingSpaceComponent.jsx @@ -17,9 +17,14 @@ import deleteIcon from "../../assets/bookinfoPage/trash.svg"; import plusIcon from "../../assets/mainPage/plus.svg"; import helpCircle from "../../assets/mainPage/help-circle.svg"; -const ReadingSpaceComponent = ({ setColor, setIsNavBar }) => { +const ReadingSpaceComponent = ({ + setColor, + setIsNavBar, + setShowDeleteModal = () => {}, + setShowOutModal = () => {}, +}) => { const navigate = useNavigate(); - const [showDeleteModal, setShowDeleteModal] = useState(false); + const [isEditMode, setIsEditMode] = useState(false); const [isHelp, setIsHelp] = useState(false); const [bottomSheetShow, setBottomSheetShow] = useState(false); @@ -69,6 +74,7 @@ const ReadingSpaceComponent = ({ setColor, setIsNavBar }) => { setCards(reorderedCards); }; + //useEffect 훅 useEffect(() => { if (isEditMode) { @@ -83,14 +89,27 @@ const ReadingSpaceComponent = ({ setColor, setIsNavBar }) => { setShowDeleteModal(false); }; + const handleOutModal = () => { + setShowOutModal(false); + handleEditMode(); + }; + const handleHelpClick = () => { setIsHelp(!isHelp); }; const handleMenuClick = () => { + if (height.get() < expandedHeight) { + api.start({ height: expandedHeight }); + } setBottomSheetShow(true); }; + const handleEditClick = () => { setIsEditMode(true); + setVisible(false); // 닫는 애니메이션 시작 + setTimeout(() => { + setBottomSheetShow(false); // 애니메이션이 끝난 후 모달 완전히 닫기 + }, 200); }; const handleDelete = () => { @@ -110,6 +129,10 @@ const ReadingSpaceComponent = ({ setColor, setIsNavBar }) => { } }; + const handleOutClick = () => { + setShowOutModal(true); + }; + return ( <>
@@ -125,12 +148,18 @@ const ReadingSpaceComponent = ({ setColor, setIsNavBar }) => {
-
+
-

리딩 스페이스

+

리딩 스페이스

{!isEditMode && isHelpVisible && ( )} + {isEditMode && ( +
+ 카드를 드래그하여 이동해보세요 +
+ )} + {isHelp && ( { {/*
{isEditMode ? "편집" : "완료"}
*/} - {isHelpVisible && ( - menu - )} + menu
@@ -208,6 +235,22 @@ const ReadingSpaceComponent = ({ setColor, setIsNavBar }) => { )}
+ {isEditMode && height.get() === expandedHeight && ( +
+ + +
+ )} {isFloatingVisible && (
@@ -232,7 +275,10 @@ const ReadingSpaceComponent = ({ setColor, setIsNavBar }) => { >
-
+
navigate("/selectcard")} + > 추가하기
@@ -261,16 +307,6 @@ const ReadingSpaceComponent = ({ setColor, setIsNavBar }) => {
- {showDeleteModal && ( - {}} - onRightClick={handleDeleteModal} - /> - )} ); }; diff --git a/bookduck/src/components/MainPage/ReviewCard.jsx b/bookduck/src/components/MainPage/ReviewCard.jsx index aa1550d..27eb8d3 100644 --- a/bookduck/src/components/MainPage/ReviewCard.jsx +++ b/bookduck/src/components/MainPage/ReviewCard.jsx @@ -1,7 +1,7 @@ import React from "react"; import emptyImage from "../../assets/recordingPage/rating-empty.svg"; import filledImage from "../../assets/recordingPage/rating-filled.svg"; -const ReviewCard = ({ selected, review, rating, title, author, onClick }) => { +const ReviewCard = ({ selected, content, rating, title, author, onClick }) => { return (
{

- {review || "한줄평 카드를 선택해주세요"} + {content || "한줄평 카드를 선택해주세요"}

{title || "제목"} / {author || "작가"} diff --git a/bookduck/src/components/NotificationPage/AnnounceNotiComponent.jsx b/bookduck/src/components/NotificationPage/AnnounceNotiComponent.jsx index daa8dac..77ec791 100644 --- a/bookduck/src/components/NotificationPage/AnnounceNotiComponent.jsx +++ b/bookduck/src/components/NotificationPage/AnnounceNotiComponent.jsx @@ -1,6 +1,10 @@ -import React from "react"; +import React, { useState, useEffect } from "react"; import NotificationItemComponent from "./NotificationItemComponent"; -const AnnounceNotiComponent = ({ notifications }) => { +import { patch } from "../../api/example"; +const AnnounceNotiComponent = () => { + //상태 관리 + const [notifications, setNotifications] = useState([]); + return notifications.map((notification) => (

{ - return notifications.map((notification) => ( -
+const GeneralNotiComponent = () => { + //상태 관리 + const [notifications, setNotifications] = useState([]); + + //API 연결 + //API-알람 목록 받아오기 + const patchAlarmList = async () => { + try { + const response = await patch(`/alarms/common`); + console.log(response); + setNotifications(response.alarmList); + } catch (error) { + console.error("알람 읽기 오류", error); + } + }; + + //useEffect 훅 + useEffect(() => { + patchAlarmList(); + console.log(notifications); + }, []); + + return notifications.map((notification, index) => ( +
)); diff --git a/bookduck/src/components/NotificationPage/NotificationItemComponent.jsx b/bookduck/src/components/NotificationPage/NotificationItemComponent.jsx index a278902..bad1db1 100644 --- a/bookduck/src/components/NotificationPage/NotificationItemComponent.jsx +++ b/bookduck/src/components/NotificationPage/NotificationItemComponent.jsx @@ -4,8 +4,9 @@ import alarmheart from "../../assets/common/heart-alarm.svg"; import alarmhand from "../../assets/common/waving-hand-alarm.svg"; import alarmbadge from "../../assets/common/badge-alarm.svg"; import alarmCircle from "../../assets/common/circle-alarm.svg"; + const notificationTemplates = { - 친구요청: { + FRIEND_REQUEST: { icon: alarmhand, message: (text) => ( @@ -14,7 +15,7 @@ const notificationTemplates = { ), }, - 친구수락: { + FRIEND_APPROVED: { icon: alarmhand, message: (text) => ( @@ -23,7 +24,7 @@ const notificationTemplates = { ), }, - 레벨업: { + LEVEL_UP: { icon: alarmduck, message: (text) => ( @@ -32,7 +33,7 @@ const notificationTemplates = { ), }, - 기록: { + ONELINELIKE_ADDED: { icon: alarmheart, message: (text) => ( @@ -41,7 +42,7 @@ const notificationTemplates = { ), }, - 업적: { + BADGE_UNLOCKED: { icon: alarmbadge, message: (text) => ( @@ -53,23 +54,54 @@ const notificationTemplates = { }, }; -const NotificationItemComponent = ({ type, text, read = false }) => { - const notificationData = notificationTemplates[type]; +const formatNotiTime = (rawTime) => { + const now = new Date(); + const past = new Date(rawTime); + + const diff = Math.floor((now - past) / 1000); // 총 시간 차이를 초 단위로 계산 + const weeks = Math.floor(diff / (3600 * 24 * 7)); // 주 단위로 계산 + const days = Math.floor(diff / (3600 * 24)); // 일 단위로 계산 + const hours = Math.floor((diff % (3600 * 24)) / 3600); // 시간 단위로 계산 + const minutes = Math.floor((diff % 3600) / 60); // 분 단위로 계산 + if (weeks > 0) { + return `${weeks}주`; + } else if (days > 0) { + return `${days}일`; + } else if (hours > 0) { + return `${hours}시간`; + } else if (minutes > 0) { + return `${minutes}분`; + } else { + return `방금 전`; + } +}; + +const NotificationItemComponent = ({ + alarmType, + boldText, + isRead, + createdTime, +}) => { + const notificationData = notificationTemplates[alarmType]; + console.log(notificationData); return (
-
- {type} - {!read && ( +
+ {alarmType} + {!isRead && ( )}
-
+
- {notificationData.message(text)} + {notificationData?.message(boldText)} + + + {formatNotiTime(createdTime)}
diff --git a/bookduck/src/components/SearchPage/SearchBookComponent.jsx b/bookduck/src/components/SearchPage/SearchBookComponent.jsx index 47701ac..9cbea34 100644 --- a/bookduck/src/components/SearchPage/SearchBookComponent.jsx +++ b/bookduck/src/components/SearchPage/SearchBookComponent.jsx @@ -1,56 +1,80 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useRef } from "react"; import { useNavigate } from "react-router-dom"; import BookListView from "../common/BookListView"; -import Divider1 from "../../components/common/Divider1"; import ButtonComponent from "../common/ButtonComponent"; import BottomSheetModal from "../common/BottomSheetModal"; +import Divider1 from "../../components/common/Divider1"; import ListBottomSheet from "../common/ListBottomSheet"; -import { get } from "../../api/example"; +import { get, patch } from "../../api/example"; + const SearchBookComponent = ({ search }) => { const navigate = useNavigate(); - const [isCancel, setIsCancel] = useState(false); const [bottomSheetShow, setBottomSheetShow] = useState(false); const [visible, setVisible] = useState(false); - const [status1, setStatus1] = useState("읽고 싶어요"); - const [status2, setStatus2] = useState("서재에 담기"); + const statusArr = ["읽고 싶어요", "읽고 있어요", "다 읽었어요", "중단했어요"]; + const [currentState, setCurrentStatus] = useState(); + const [selectedBookId, setSelectedBookId] = useState(); + const [isCancel, setIsCancel] = useState(false); + const [registeredBooks, setRegisteredBooks] = useState([]); const [books, setBooks] = useState([]); + const [currentPage, setCurrentPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + const loaderRef = useRef(null); + const DATA_LIMIT = 10; + + const getReadingStatusKey = (status) => { + switch (status) { + case "읽고 싶어요": + return "NOT_STARTED"; + case "읽고 있어요": + return "READING"; + case "다 읽었어요": + return "FINISHED"; + case "중단했어요": + return "STOPPED"; + default: + return "NOT_STARTED"; + } + }; //API연결 //API-등록 책 정보받기 - const getRegisteredBooks = async (keyword, page, size) => { + const getRegisteredBooks = async (keyword, page = 1) => { try { - let data = []; const response = await get( - `/bookinfo/search/custom?keyword=${keyword}&page=${page}&size=${size}` + `/bookinfo/search/custom?keyword=${encodeURIComponent( + keyword + )}&page=${page}&size=${DATA_LIMIT}` ); - console.log(response); - if (response.bookCount > 0) { - data = response.bookList.map((book) => ({ - id: book.bookinfoId, - title: book.title, - author: book.author, - img: book.imgPath, - myRating: book.myRating, - readStatus: book.readStatus, - })); - } - + console.log("response", response); + const data = response.bookList.map((book) => ({ + bookinfoId: book.bookinfoId, + title: book.title, + author: book.author, + imgPath: book.imgPath, + myRating: book.myRating, + readStatus: book.readStatus, + })); console.log("data", data); - setRegisteredBooks(data); + + setRegisteredBooks((b) => [...b, data]); console.log("registeredBooks:", registeredBooks); } catch (error) { console.error("등록 책 읽어오기 오류", error); } }; - - //API-일반 책 정보받기 - const getBooks = async (keyword, page, size) => { + // API-일반 책 받아오기 + const getBooks = async (keyword, page = 1) => { + if (!keyword) return; try { const response = await get( - `/bookinfo/search?keyword=${keyword}&page=${page}&size=${size}` + `/bookinfo/search?keyword=${encodeURIComponent( + keyword + )}&page=${page}&size=${DATA_LIMIT}` ); + console.log(response); const data = response.bookList.map((book) => ({ id: book.bookinfoId, title: book.title, @@ -59,34 +83,95 @@ const SearchBookComponent = ({ search }) => { myRating: book.myRating, readStatus: book.readStatus, })); - setBooks(data); - console.log("books:", books); + + setBooks((b) => [...b, ...data]); + setTotalPages(response.totalPages || 1); // 서버에서 전체 페이지 수 반환 + } catch (error) { + console.error("책 데이터 불러오기 오류:", error); + } + }; + + //API-직접 등록 책 상태 변경 + const patchRegisteredStatus = async (selectedBookId, currentStatus) => { + try { + await patch(`/books/${selectedBookId}?status=${currentStatus}`); } catch (error) { - console.error("일반 책 읽어오기 오류", error); + console.error(error); } }; - //useEffect 훅 + // useEffect 훅 + // 검색어 변경 시 데이터 초기화 및 첫 페이지 호출 useEffect(() => { if (search) { - // getRegisteredBooks(search, 1, 10); - getBooks(search, 1, 10); + setBooks([]); // 기존 데이터 초기화 + setCurrentPage(1); // 첫 페이지로 초기화 + getBooks(search, 1); + setRegisteredBooks([]); + getRegisteredBooks(search); } }, [search]); + // 무한 스크롤 감지 + useEffect(() => { + const handleObserver = (entries) => { + const [entry] = entries; + if (entry.isIntersecting && currentPage < totalPages) { + console.log("다음 페이지 로드"); + setCurrentPage((p) => p + 1); + } + }; + + const observer = new IntersectionObserver(handleObserver, { + root: null, // viewport 사용 + rootMargin: "0px", + threshold: 1.0, + }); + + if (loaderRef.current) observer.observe(loaderRef.current); + + return () => observer.disconnect(); + }, [currentPage, totalPages]); + + // 현재 페이지 데이터 로드 + useEffect(() => { + if (currentPage > 1 && search) { + console.log(`페이지 ${currentPage} 데이터 로드`); + getBooks(search, currentPage); + } + }, [currentPage, search]); + //이벤트 핸들러 - const handleOpenClick1 = () => { - setIsCancel(false); + const handleStatusClick = (id) => { + console.log("id", id); + setSelectedBookId(id); + + console.log("id", selectedBookId); setBottomSheetShow(true); }; - const handleOpenClick2 = (inLibary) => { - setIsCancel(inLibary); - setBottomSheetShow(true); + /*직접 등록 책 처음 등록*/ + const handleStatusRegister = async (status) => { + setCurrentStatus(status); + const res = await patchRegisteredStatus( + selectedBookId, + getReadingStatusKey(status) + ); + console.log(res); + setVisible(false); + setTimeout(() => { + setBottomSheetShow(false); + }, 200); }; - const handleStatusChange = (newStatus) => { - setStatus(newStatus); + /*직접 등록 책 상태 변경*/ + const handleStatusChange = async (status) => { + setCurrentStatus(status); + const res = await patchRegisteredStatus( + selectedBookId, + getReadingStatusKey(status) + ); + console.log(res); setVisible(false); setTimeout(() => { setBottomSheetShow(false); @@ -98,35 +183,37 @@ const SearchBookComponent = ({ search }) => { {registeredBooks.length > 0 || books.length > 0 ? ( <>
+ {/* 직접 등록한 책 */} {registeredBooks.length > 0 && - registeredBooks.map((book) => ( + registeredBooks.map((book, index) => ( handleStatusClick(book.bookinfoId)} /> ))}
{registeredBooks.length > 0 && books.length > 0 && } + {/* 일반 책 */}
{books.length > 0 && - books.map((book) => ( + books.map((book, index) => ( handleOpenClick2(book.inLibrary)} + handleStatusClick={() => handleStatusClick(book.userBookdId)} /> ))}
@@ -159,13 +246,15 @@ const SearchBookComponent = ({ search }) => { visible={visible} setVisible={setVisible} > - +
+ +
); diff --git a/bookduck/src/components/common/BottomSheetModal.jsx b/bookduck/src/components/common/BottomSheetModal.jsx index c10f7f6..da801d6 100644 --- a/bookduck/src/components/common/BottomSheetModal.jsx +++ b/bookduck/src/components/common/BottomSheetModal.jsx @@ -32,6 +32,7 @@ const BottomSheetModal = ({ visible, setVisible, children, + custom = false, }) => { // 모달이 열릴 때 visible을 true로 설정해 애니메이션을 실행 useEffect(() => { @@ -53,21 +54,23 @@ const BottomSheetModal = ({ const slideModal = (
-
+ )} + +
e.stopPropagation()} + className={`bg-white ${ + visible ? "animate-slideUp" : "animate-slideDown" + } bg-opacity-100 absolute bottom-0 w-[24.5625rem] h-fit rounded-t-xl pt-8 pb-4 transition-transform shadow-custom duration-300`} > -
e.stopPropagation()} - className={`bg-white ${ - visible ? "animate-slideUp" : "animate-slideDown" - } bg-opacity-100 absolute bottom-0 w-[24.5625rem] h-fit rounded-t-xl pt-8 pb-4 transition-transform duration-300`} - > - {children} -
-
+ {children} +
); diff --git a/bookduck/src/components/common/TabBarComponent.jsx b/bookduck/src/components/common/TabBarComponent.jsx index c3d500b..5c04715 100644 --- a/bookduck/src/components/common/TabBarComponent.jsx +++ b/bookduck/src/components/common/TabBarComponent.jsx @@ -14,7 +14,7 @@ const TabBarComponent = ({ onTabClick, size = "big", borderWidth, - isNoti = "false", + isNoti = false, ...props }) => { const isBig = size === "big"; @@ -52,7 +52,7 @@ const TabBarComponent = ({ )}
))} - {!isNoti ? ( + {isNoti ? ( diff --git a/bookduck/src/components/common/TextField.jsx b/bookduck/src/components/common/TextField.jsx index 8fe1182..2326bd1 100644 --- a/bookduck/src/components/common/TextField.jsx +++ b/bookduck/src/components/common/TextField.jsx @@ -14,6 +14,7 @@ const TextField = ({ check = true, error = null, inputError, + nickname = false, handleEdit, isSubmitted, defaultType, @@ -54,7 +55,7 @@ const TextField = ({
)}
- {!defaultType && !isSubmitted && ( + {nickname && !defaultType && !isSubmitted && (

{ const [tab, setTab] = useState("친구"); - // const friendList = [ - // { id: "1", userName: "유저1" }, - // { id: "2", userName: "유저2" }, - // { id: "3", userName: "유저3" }, - // { id: "4", userName: "유저4" }, - // { id: "5", userName: "유저5" }, - // ]; return (

diff --git a/bookduck/src/pages/MainPage/MainPage.jsx b/bookduck/src/pages/MainPage/MainPage.jsx index ee2db43..0b51e4a 100644 --- a/bookduck/src/pages/MainPage/MainPage.jsx +++ b/bookduck/src/pages/MainPage/MainPage.jsx @@ -17,6 +17,8 @@ const MainPage = () => { const [userInfo, setUserInfo] = useState(null); const [color, setColor] = useState("bg-gray-50"); const [isNavBar, setIsNavBar] = useState("true"); + const [showDeleteModal, setShowDeleteModal] = useState(false); + const [showOutModal, setShowOutModal] = useState(false); //API 연결 const getUserInfo = async (userId) => { @@ -77,9 +79,33 @@ const MainPage = () => { arrow - +
{isNavBar && } + {showDeleteModal && ( + {}} + onRightClick={handleDeleteModal} + /> + )} + {showOutModal && ( + {}} + /> + )}
); }; diff --git a/bookduck/src/pages/MainPage/SelectCardPage.jsx b/bookduck/src/pages/MainPage/SelectCardPage.jsx index 538aa5c..7445448 100644 --- a/bookduck/src/pages/MainPage/SelectCardPage.jsx +++ b/bookduck/src/pages/MainPage/SelectCardPage.jsx @@ -2,9 +2,9 @@ import React from "react"; import { useNavigate } from "react-router-dom"; import StatusBar from "../../components/common/StatusBar"; import Header3 from "../../components/common/Header3"; -import OneBookCard from "../../components/MainPage/OneBookCard"; import ExtractCard from "../../components/MainPage/ExtractCard"; import ReviewCard from "../../components/MainPage/ReviewCard"; +import BookDisplay from "../../components/MainPage/BookDisplay"; const SelectCardPage = () => { const navigate = useNavigate(); @@ -25,14 +25,14 @@ const SelectCardPage = () => { navigate("/selectcard/custom", { state: { bookNumber: 1 } }) } > - +
navigate("/selectcard/custom", { state: { bookNumber: 2 } }) } > - +
navigate("/selectcard/extract")} /> navigate("/selectcard/review")} /> diff --git a/bookduck/src/pages/MainPage/SelectCustomPage.jsx b/bookduck/src/pages/MainPage/SelectCustomPage.jsx index fa2f969..4e2cb6d 100644 --- a/bookduck/src/pages/MainPage/SelectCustomPage.jsx +++ b/bookduck/src/pages/MainPage/SelectCustomPage.jsx @@ -1,43 +1,131 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { useLocation } from "react-router-dom"; +import { get, post } from "../../api/example"; import StatusBar from "../../components/common/StatusBar"; import Header3 from "../../components/common/Header3"; import OneBookCard from "../../components/MainPage/OneBookCard"; import BottomSheetModal from "../../components/common/BottomSheetModal"; import SearchComponent from "../../components/common/SearchComponent"; import BookListView from "../../components/common/BookListView"; +import ButtonComponent from "../../components/common/ButtonComponent"; const SelectCustomPage = () => { + //상태 관리 const location = useLocation(); - const bookNumber = location.state?.bookNumber; - const cards = [ - { - id: 1, - content: "한번 피면 광장을 보게 되는 책이다.", - title: "가나다", - author: "마바사", - }, - { id: 2, content: "또 다른 책 제목", title: "가나다", author: "마바사" }, - ]; + const bookNumber = location.state?.bookNumber; + const [selected, setSelected] = useState("firstBook"); + const [firstImg, setFirstImg] = useState(); + const [secondImg, setSecondImg] = useState(); + const [firstId, setFirstId] = useState(); + const [secondId, setSecondId] = useState(); const [search, setSearch] = useState(""); const [bottomSheetShow, setBottomSheetShow] = useState(true); const [visible, setVisible] = useState(true); - const [selected, setSelected] = useState("firstBook"); + const [books, setBooks] = useState([]); + const [enabled, setEnabled] = useState(false); + + const [cardData, setCardData] = useState({ + cardType: "BOOK_WITH_SONG", + resourceId1: null, + resourceId2: null, + text1: "", + text2: "", + text3: "", + }); + + //API 연결 + //리스트 책 받아오기 + const getBooks = async () => { + try { + const response = await get(`/books/list?sort=latest`); + setBooks(response.bookList); + console.log(response); + } catch (error) { + console.error(error); + } + }; + + // 카드 등록하기 + const postCard = async () => { + try { + console.log(cardData); + const response = await post(`/readingspace`, cardData); + console.log("Card successfully posted:", response); + } catch (error) { + console.error("Error posting card:", error); + } + }; + + //useEffect hook + useEffect(() => { + getBooks(); + }, []); + + //첫번째 책 아이디 + useEffect(() => { + if (firstId) { + console.log("선택된 책 ID 1:", firstId); + } + }, [firstId]); + + //두번째 책 아이디 + useEffect(() => { + if (secondId) { + console.log("선택된 책 ID 2:", secondId); + } + }, [secondId]); + + //enabled + useEffect(() => { + console.log(enabled); + }, [enabled]); + + //이벤트 핸들러 + const handleStatusClick = (bookInfoId, bookImg) => { + if (selected === "firstBook") { + setFirstId(bookInfoId); + setFirstImg(bookImg); + } else if (selected === "secondBook") { + setSecondId(bookInfoId); + setSecondImg(bookImg); + } + + setVisible(false); + setTimeout(() => { + setBottomSheetShow(false); + }, 200); + }; return ( -
+
+
+
+

꾸미기 카드는 수정이 불가능해요.

+

꼼꼼히 확인해주세요:)

+
{ bottomSheetShow={bottomSheetShow} setBottomSheetShow={setBottomSheetShow} visible={visible} + custom={true} setVisible={setVisible} > { placeholder="서재에 담긴 책을 검색하세요" />
- {cards.map((card) => ( + {books.map((book, index) => ( + handleStatusClick(book.bookInfoId, book.imgPath) + } /> ))}
diff --git a/bookduck/src/pages/MainPage/SelectExtractPage.jsx b/bookduck/src/pages/MainPage/SelectExtractPage.jsx index 62a0df4..88531d3 100644 --- a/bookduck/src/pages/MainPage/SelectExtractPage.jsx +++ b/bookduck/src/pages/MainPage/SelectExtractPage.jsx @@ -1,4 +1,5 @@ -import React, { useState } from "react"; +import React, { useState, useEffect, useRef } from "react"; +import { get, post } from "../../api/example"; import StatusBar from "../../components/common/StatusBar"; import Header3 from "../../components/common/Header3"; import SearchComponent from "../../components/common/SearchComponent"; @@ -6,23 +7,117 @@ import ExtractCard from "../../components/MainPage/ExtractCard"; import ButtonComponent from "../../components/common/ButtonComponent"; const SelectExtractPage = () => { + //상태 관리 const [search, setSearch] = useState(""); - const [selectedCard, setSelectedCard] = useState(""); + const [excerpts, setExcerpts] = useState([]); + const [excerptId, setExcerptId] = useState(); + const [currentPage, setCurrentPage] = useState(0); + const [totalPages, setTotalPages] = useState(0); + const [cardData, setCardData] = useState({ + cardType: "EXCERPT", + }); + + const loaderRef = useRef(null); + const DATA_LIMIT = 10; + + //API 연결 + //발췌 리스트 받아오기 + const getExcerpts = async (keyword, page = 0) => { + try { + const response = await get( + `/readingspace/excerpts/search?keyword=${keyword}&page=${page}&size=${DATA_LIMIT}` + ); + + console.log("response", response); + const data = response.pageContent.map((excerpt) => ({ + author: excerpt.author, + excerptContent: excerpt.excerptContent, + excerptId: excerpt.excerptId, + visibility: excerpt.visibility, + pageNumber: excerpt.pageNumber, + title: excerpt.title, + author: excerpt.author, + })); + + if (page === 0) { + // 첫 페이지인 경우 기존 데이터 초기화 + setExcerpts(data); + } else { + // 다음 페이지 데이터를 추가 + setExcerpts((prev) => [...prev, ...data]); + } + + setTotalPages(response.totalPages || 0); // 서버에서 전체 페이지 수 반환 + } catch (error) { + console.error(error); + } + }; + + // 카드 등록하기 + const postCard = async () => { + try { + console.log(cardData); + const response = await post(`/readingspace`, cardData); + console.log("Card successfully posted:", response); + } catch (error) { + console.error("Error posting card:", error); + } + }; + + // useEffect 훅 + // 검색어 변경 시 데이터 초기화 및 첫 페이지 호출 + + useEffect(() => { + console.log("excerpts", excerpts); + }, [excerpts]); + + useEffect(() => { + if (search) { + setExcerpts([]); // 기존 데이터 초기화 + setCurrentPage(0); // 첫 페이지로 초기화 + getExcerpts(search, 0); + } + }, [search]); + + // 무한 스크롤 감지 + useEffect(() => { + const handleObserver = (entries) => { + const [entry] = entries; + if (entry.isIntersecting && currentPage < totalPages) { + console.log("다음 페이지 로드"); + setCurrentPage((p) => p + 1); + } + }; + + const observer = new IntersectionObserver(handleObserver, { + root: null, // viewport 사용 + rootMargin: "0px", + threshold: 1.0, + }); + + if (loaderRef.current) observer.observe(loaderRef.current); + + return () => observer.disconnect(); + }, [currentPage, totalPages]); + + // 현재 페이지 데이터 로드 + useEffect(() => { + if (currentPage > 0 && search) { + console.log(`페이지 ${currentPage} 데이터 로드`); + getExcerpts(search, currentPage); + } + }, [currentPage, search]); + + //카드 데이터 업데이트 + useEffect(() => { + setCardData((c) => ({ ...c, resourceId1: excerptId })); + }, [excerptId]); + + //이벤트 핸들러 const handleSelectCard = (id) => { - setSelectedCard(id); + setExcerptId(id); }; - const cards = [ - { - id: 1, - content: "한번 피면 광장을 보게 되는 책이다.", - title: "가나다", - author: "마바사", - }, - { id: 2, content: "또 다른 책 제목", title: "가나다", author: "마바사" }, - { id: 3, content: "다른 책 제목", title: "가나다", author: "마바사" }, - { id: 4, content: "다른 책 제목", title: "가나다", author: "마바사" }, - { id: 5, content: "다른 책 제목", title: "가나다", author: "마바사" }, - ]; + return (
@@ -35,24 +130,27 @@ const SelectExtractPage = () => { />
- {cards.map((card) => ( - handleSelectCard(card.id)} - key={card.id} - selected={selectedCard === card.id ? true : false} - title={card.title} - content={card.content} - page={card.page} - author={card.author} - /> - ))} + {excerpts.length > 0 && + excerpts.map((excerpt, index) => ( + handleSelectCard(excerpt.excerptId)} + selected={excerptId === excerpt.excerptId ? true : false} + content={excerpt.excerptContent} + visibility={excerpt.visibility} + pageNumber={excerpt.pageNumber} + title={excerpt.title} + author={excerpt.author} + /> + ))}
- {selectedCard && ( + {excerptId && (
)} diff --git a/bookduck/src/pages/MainPage/SelectReviewPage.jsx b/bookduck/src/pages/MainPage/SelectReviewPage.jsx index d9f0395..4a2c663 100644 --- a/bookduck/src/pages/MainPage/SelectReviewPage.jsx +++ b/bookduck/src/pages/MainPage/SelectReviewPage.jsx @@ -1,4 +1,5 @@ -import React, { useState } from "react"; +import React, { useState, useEffect, useRef } from "react"; +import { get, post } from "../../api/example"; import StatusBar from "../../components/common/StatusBar"; import Header3 from "../../components/common/Header3"; import SearchComponent from "../../components/common/SearchComponent"; @@ -6,49 +7,110 @@ import ReviewCard from "../../components/MainPage/ReviewCard"; import ButtonComponent from "../../components/common/ButtonComponent"; const SelectReviewPage = () => { + //상태 관리 const [search, setSearch] = useState(""); - const [selectedCard, setSelectedCard] = useState(""); + const [reviews, setReviews] = useState([]); + const [reviewId, setReviewId] = useState(); + const [currentPage, setCurrentPage] = useState(0); + const [totalPages, setTotalPages] = useState(0); + const loaderRef = useRef(null); + const DATA_LIMIT = 10; + const [cardData, setCardData] = useState({ + cardType: "ONELINE", + }); + + //API 연결 + //리뷰 리스트 받아오기 + const getReviews = async (keyword, page = 0) => { + try { + const response = await get( + `/readingspace/onelines/search?keyword=${keyword}&page=${page}&size=${DATA_LIMIT}` + ); + console.log("response", response); + const data = response.pageContent.map((review) => ({ + oneLineId: review.oneLineId, + oneLineContent: review.oneLineContent, + rating: review.rating, + title: review.title, + author: review.author, + })); + if (page === 0) { + // 첫 페이지인 경우 기존 데이터 초기화 + setReviews(data); + } else { + // 다음 페이지 데이터를 추가 + setReviews((prev) => [...prev, ...data]); + } + + setTotalPages(response.totalPages || 0); // 서버에서 전체 페이지 수 반환 + } catch (error) { + console.error(error); + } + }; + + // 카드 등록하기 + const postCard = async () => { + try { + console.log(cardData); + const response = await post(`/readingspace`, cardData); + console.log("Card successfully posted:", response); + } catch (error) { + console.error("Error posting card:", error); + } + }; + + //useEffect 훅 + useEffect(() => { + console.log("reviews", reviews); + }, [reviews]); + + useEffect(() => { + if (search) { + setReviews([]); // 기존 데이터 초기화 + setCurrentPage(0); // 첫 페이지로 초기화 + getReviews(search, 0); + } + }, [search]); + + // 무한 스크롤 감지 + useEffect(() => { + const handleObserver = (entries) => { + const [entry] = entries; + if (entry.isIntersecting && currentPage < totalPages) { + console.log("다음 페이지 로드"); + setCurrentPage((p) => p + 1); + } + }; + + const observer = new IntersectionObserver(handleObserver, { + root: null, // viewport 사용 + rootMargin: "0px", + threshold: 1.0, + }); + + if (loaderRef.current) observer.observe(loaderRef.current); + + return () => observer.disconnect(); + }, [currentPage, totalPages]); + + // 현재 페이지 데이터 로드 + useEffect(() => { + if (currentPage > 0 && search) { + console.log(`페이지 ${currentPage} 데이터 로드`); + getBooks(search, currentPage); + } + }, [currentPage, search]); + + //카드 데이터 업데이트 + useEffect(() => { + setCardData((c) => ({ ...c, resourceId1: reviewId })); + }, [reviewId]); + + //이벤트 핸들러 const handleSelectCard = (id) => { - setSelectedCard(id); + setReviewId(id); }; - const cards = [ - { - id: 1, - rating: 2, - review: "한번 피면 광장을 보게 되는 책이다.", - title: "가나다", - author: "마바사", - }, - { - id: 2, - review: "또 다른 책 제목", - rating: 2, - title: "가나다", - author: "마바사", - }, - { - id: 3, - review: "다른 책 제목", - rating: 2, - title: "가나다", - author: "마바사", - }, - { - id: 4, - review: "다른 책 제목", - rating: 2, - title: "가나다", - author: "마바사", - }, - { - id: 5, - review: "다른 책 제목", - rating: 2, - title: "가나다", - author: "마바사", - }, - ]; return (
@@ -61,24 +123,25 @@ const SelectReviewPage = () => { />
- {cards.map((card) => ( + {reviews.map((review, index) => ( handleSelectCard(card.id)} + key={index} + selected={reviewId === review.oneLineId ? true : false} + content={review.oneLineContent} + rating={review.rating} + title={review.title} + author={review.author} + onClick={() => handleSelectCard(review.oneLineId)} /> ))}
- {selectedCard && ( + {reviewId && (
)} diff --git a/bookduck/src/pages/NotificationPage/NotificationPage.jsx b/bookduck/src/pages/NotificationPage/NotificationPage.jsx index b595852..07f92ed 100644 --- a/bookduck/src/pages/NotificationPage/NotificationPage.jsx +++ b/bookduck/src/pages/NotificationPage/NotificationPage.jsx @@ -7,34 +7,24 @@ import AnnounceNotiComponent from "../../components/NotificationPage/AnnounceNot const NotificationPage = () => { const [tab, setTab] = useState("일반"); - const [notifications, setNotifications] = useState([]); - - useEffect(() => { - const fetchedNotifications = [ - { id: 1, type: "친구요청", text: "찬희", read: false }, - { id: 2, type: "친구수락", text: "찬희", read: false }, - { id: 3, type: "레벨업", text: "2", read: true }, - { id: 4, type: "기록", text: "찬희", read: true }, - { id: 5, type: "업적", text: "ㅇㅇ", read: true }, - ]; - setNotifications(fetchedNotifications); - }, []); return ( -
+
- - {tab === "일반" && } - {tab === "공지" && ( - - )} +
+ +
+ + {tab === "일반" && } + {tab === "공지" && }
); }; diff --git a/bookduck/src/pages/SearchPage/SearchMainPage.jsx b/bookduck/src/pages/SearchPage/SearchMainPage.jsx index 9d772c1..99e1cd7 100644 --- a/bookduck/src/pages/SearchPage/SearchMainPage.jsx +++ b/bookduck/src/pages/SearchPage/SearchMainPage.jsx @@ -1,33 +1,43 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; +import { get } from "../../api/example"; import { useNavigate } from "react-router-dom"; import StatusBar from "../../components/common/StatusBar"; import SearchComponent from "../../components/common/SearchComponent"; import BookComponent from "../../components/SearchPage/BookComponent"; import ButtonComponent from "../../components/common/ButtonComponent"; import CarouselComponent from "../../components/SearchPage/CarouselComponent"; -import BottomNavbar from "../../components/common/BottomNavbar"; import SearchBookComponent from "../../components/SearchPage/SearchBookComponent"; import SearchArchiveComponent from "../../components/SearchPage/SearchArchiveComponent"; import SearchUserComponent from "../../components/SearchPage/SearchUserComponent"; import TabBarComponent from "../../components/common/TabBarComponent"; -import { get, patch, del, post } from "../../api/example"; -import duck from "../../assets/common/duck.svg"; const SearchMainPage = () => { const navigate = useNavigate(); const [search, setSearch] = useState(""); const [submittedSearch, setSubmittedSearch] = useState(""); const [tab, setTab] = useState("책"); - const recentBooks = [ - { id: "1", img: duck, title: "가나다라" }, - { id: "2", img: duck, title: "마바사" }, - { id: "3", img: duck, title: "큐큐" }, - ]; + const [recentBooks, setRecentBooks] = useState([]); + + //API 연결 + const getRecentBooks = async () => { + try { + const response = await get(`/books/recent`); + setRecentBooks(response.bookList); + } catch (error) { + console.error("최근 책 정보 읽기 오류", error); + } + }; + + //useEffect 훅 + useEffect(() => { + getRecentBooks(); + }, []); //이벤트 핸들러 const handleSearch = () => { setSubmittedSearch(search); }; + return (
@@ -41,11 +51,11 @@ const SearchMainPage = () => {
최근 기록한 책
- {recentBooks.map((book) => { + {recentBooks.map((book, index) => { return ( ); @@ -102,8 +112,6 @@ const SearchMainPage = () => { )}
)} - -
); }; diff --git a/bookduck/src/pages/SettingPage/SettingPage.jsx b/bookduck/src/pages/SettingPage/SettingPage.jsx index cedf4ad..35ceeff 100644 --- a/bookduck/src/pages/SettingPage/SettingPage.jsx +++ b/bookduck/src/pages/SettingPage/SettingPage.jsx @@ -329,6 +329,7 @@ const SettingPage = () => { inputError={inputError} handleEdit={handleEdit} isSubmitted={isSubmitted} + nickname={true} defaultType={false} handleValue={(e) => handleValue(e)} inputValue={inputValue}