From 746b46285dea9a8e9ce2ca97749932f09fc5aa50 Mon Sep 17 00:00:00 2001 From: inji0212 Date: Mon, 4 Nov 2024 11:29:36 +0900 Subject: [PATCH 01/35] =?UTF-8?q?feat=20review=20page=20=EB=A9=94=EB=89=B4?= =?UTF-8?q?=EB=B0=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CategoryList/CategoryItems/index.tsx | 29 +++++++++++ .../FilterSection/CategoryList/index.tsx | 20 +++++++ .../FilterSection/CloseDateToggle/index.tsx | 24 +++++++++ .../FilterSection/DateDropdown/index.tsx | 52 +++++++++++++++++++ .../review/FilterSection/FiterList/index.tsx | 16 ++++++ .../FilterSection/RegionDropdown/index.tsx | 29 +++++++++++ src/components/review/FilterSection/index.tsx | 22 ++++++++ src/pages/review/index.tsx | 28 +++++++++- 8 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 src/components/review/FilterSection/CategoryList/CategoryItems/index.tsx create mode 100644 src/components/review/FilterSection/CategoryList/index.tsx create mode 100644 src/components/review/FilterSection/CloseDateToggle/index.tsx create mode 100644 src/components/review/FilterSection/DateDropdown/index.tsx create mode 100644 src/components/review/FilterSection/FiterList/index.tsx create mode 100644 src/components/review/FilterSection/RegionDropdown/index.tsx create mode 100644 src/components/review/FilterSection/index.tsx diff --git a/src/components/review/FilterSection/CategoryList/CategoryItems/index.tsx b/src/components/review/FilterSection/CategoryList/CategoryItems/index.tsx new file mode 100644 index 00000000..9fe024c7 --- /dev/null +++ b/src/components/review/FilterSection/CategoryList/CategoryItems/index.tsx @@ -0,0 +1,29 @@ +import Image from 'next/image'; + +interface CategoryItemsProps { + handleCategoryClick: (category: string) => void; + option: { icon: string; id: string; label: string }; + selectedCategory: string; +} + +export default function CategoryItems({ handleCategoryClick, selectedCategory, option }: CategoryItemsProps) { + return ( +
{ + handleCategoryClick(option.label); + }} + > + + +
+ ); +} diff --git a/src/components/review/FilterSection/CategoryList/index.tsx b/src/components/review/FilterSection/CategoryList/index.tsx new file mode 100644 index 00000000..631e3553 --- /dev/null +++ b/src/components/review/FilterSection/CategoryList/index.tsx @@ -0,0 +1,20 @@ +import CategoryItems from '@/components/review/FilterSection/CategoryList/CategoryItems'; +import { FILTER_OPTIONS } from '@/constants/main/contants'; + +interface CategoryListProps { + handleCategoryClick: (category: string) => void; + selectedCategory: string; +} + +export default function CategoryList({ handleCategoryClick, selectedCategory }: CategoryListProps) { + return ( +
+
+ filter + {FILTER_OPTIONS.map((option) => ( + + ))} +
+
+ ); +} diff --git a/src/components/review/FilterSection/CloseDateToggle/index.tsx b/src/components/review/FilterSection/CloseDateToggle/index.tsx new file mode 100644 index 00000000..8f1a30b2 --- /dev/null +++ b/src/components/review/FilterSection/CloseDateToggle/index.tsx @@ -0,0 +1,24 @@ +import { useState } from 'react'; +import Image from 'next/image'; + +export default function CloseDateToggle() { + const [closeDateToggle, setCloseDateToggle] = useState(false); // 마감임박 토글 상태 + + const handleCloseFilterToggle = () => { + // 마감 임박 토글 클릭 + const newCloseDateState = !closeDateToggle; + setCloseDateToggle(newCloseDateState); + + // console.log(newCloseDateState ? 'closeDate' : ''); + }; + + return ( +
+ 마감 임박순 + 마감임박 +
+ ); +} diff --git a/src/components/review/FilterSection/DateDropdown/index.tsx b/src/components/review/FilterSection/DateDropdown/index.tsx new file mode 100644 index 00000000..40b03b93 --- /dev/null +++ b/src/components/review/FilterSection/DateDropdown/index.tsx @@ -0,0 +1,52 @@ +import { useState } from 'react'; +import Dropdown from '@/components/main/Dropdown'; +import Calendar from '@/components/shared/Calendar'; + +export default function DateDropdown() { + const [dateDropOpen, setDateDropOpen] = useState(false); + const [selectedDates, setSelectedDates] = useState<{ rangeEnd?: string; rangeStart?: string; selectedDate?: string }>({}); + + const handleDateChange = (data: { rangeEnd?: string; rangeStart?: string; selectedDate?: string }) => { + // 날짜 변경감지 + setSelectedDates(data); + }; + + const handleSubmit = () => { + // 날짜 적용하기 제출 버튼 + const { rangeStart, rangeEnd } = selectedDates; + if (!rangeStart || !rangeEnd) { + // console.log('날짜가 선택되지 않았습니다. 범위를 선택해주세요.'); + } else { + // console.log(`선택된 범위: 시작 날짜 ${rangeStart}, 종료 날짜 ${rangeEnd}`); + // 여기에 API 요청 로직 추가 가능 + } + setDateDropOpen(false); + }; + + return ( + + 모임 + 날짜 + + } + className="left-date-calendar" + > +
+ +
+ + +
+
+
+ ); +} diff --git a/src/components/review/FilterSection/FiterList/index.tsx b/src/components/review/FilterSection/FiterList/index.tsx new file mode 100644 index 00000000..8149e055 --- /dev/null +++ b/src/components/review/FilterSection/FiterList/index.tsx @@ -0,0 +1,16 @@ +import CloseDateToggle from '@/components/main/FilterSection/CloseDateToggle'; +import DateDropdown from '@/components/main/FilterSection/DateDropdown'; +import RegionDropdown from '@/components/main/FilterSection/RegionDropdown'; + +export default function FilterList() { + return ( +
+
+ + +
+ + +
+ ); +} diff --git a/src/components/review/FilterSection/RegionDropdown/index.tsx b/src/components/review/FilterSection/RegionDropdown/index.tsx new file mode 100644 index 00000000..ba79a349 --- /dev/null +++ b/src/components/review/FilterSection/RegionDropdown/index.tsx @@ -0,0 +1,29 @@ +import { useState } from 'react'; +import Dropdown from '@/components/main/Dropdown'; +import { REGION_DATA } from '@/constants/main/contants'; + +export default function RegionDropdown() { + const [regionDropOpen, setRegionDropOpen] = useState(false); + + // const handleRegionSelect = (region: string) => { + // // 지역 필터 + // console.log('선택된 지역:', region); + // setRegionDropOpen(false); + // }; + + return ( + +
    + {REGION_DATA.map((region) => ( +
  • handleRegionSelect(region)} + className="cursor-pointer p-2 text-13-15-response font-semibold text-gray-900 hover:bg-gray-50" + > + {region} +
  • + ))} +
+
+ ); +} diff --git a/src/components/review/FilterSection/index.tsx b/src/components/review/FilterSection/index.tsx new file mode 100644 index 00000000..42c6135b --- /dev/null +++ b/src/components/review/FilterSection/index.tsx @@ -0,0 +1,22 @@ +import CategoryList from '@/components/review/FilterSection/CategoryList'; + +import FilterList from './FiterList'; + +interface FilterSectionProps { + handleCategoryClick: (category: string) => void; + selectedCategory: string; +} + +export default function FilterSection({ handleCategoryClick, selectedCategory }: FilterSectionProps) { + return ( +
+ {/* 카테고리 */} + + + {/* 필터 */} +
+ +
+
+ ); +} diff --git a/src/pages/review/index.tsx b/src/pages/review/index.tsx index 53719315..8577251c 100644 --- a/src/pages/review/index.tsx +++ b/src/pages/review/index.tsx @@ -1,3 +1,29 @@ +import { useState } from 'react'; +import HeaderSection from '@/components/main/HeaderSection'; +import MainContainer from '@/components/main/MainContainer'; +import FilterSection from '@/components/review/FilterSection'; +import RootLayout from '@/components/shared/RootLayout'; + export default function Home() { -return

hi

; + const [searchValue, setSearchValue] = useState(''); + const [selectedCategory, setSelectedCategory] = useState('전체'); // 카테고리 + + const handleCategoryClick = (category: string) => { + // 카테고리 클릭 + setSelectedCategory(category); + }; + return ( +
+ + + {/* Header (타이틀, 검색창) */} + + + {/* 카테고리 */} + + + + +
+ ); } From 599b82988a6d730ba2772cae716fcc2a76661887 Mon Sep 17 00:00:00 2001 From: inji0212 Date: Mon, 4 Nov 2024 18:22:23 +0900 Subject: [PATCH 02/35] =?UTF-8?q?feat=20review=20=EB=A9=94=EB=89=B4?= =?UTF-8?q?=EB=B0=94=20ui=20=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/FilterSection/FiterList/index.tsx | 16 ---------- src/components/review/FilterSection/index.tsx | 20 ++++++++---- src/components/review/ReviewRating/index.tsx | 31 +++++++++++++++++++ src/pages/review/index.tsx | 5 ++- 4 files changed, 47 insertions(+), 25 deletions(-) delete mode 100644 src/components/review/FilterSection/FiterList/index.tsx create mode 100644 src/components/review/ReviewRating/index.tsx diff --git a/src/components/review/FilterSection/FiterList/index.tsx b/src/components/review/FilterSection/FiterList/index.tsx deleted file mode 100644 index 8149e055..00000000 --- a/src/components/review/FilterSection/FiterList/index.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import CloseDateToggle from '@/components/main/FilterSection/CloseDateToggle'; -import DateDropdown from '@/components/main/FilterSection/DateDropdown'; -import RegionDropdown from '@/components/main/FilterSection/RegionDropdown'; - -export default function FilterList() { - return ( -
-
- - -
- - -
- ); -} diff --git a/src/components/review/FilterSection/index.tsx b/src/components/review/FilterSection/index.tsx index 42c6135b..461aae85 100644 --- a/src/components/review/FilterSection/index.tsx +++ b/src/components/review/FilterSection/index.tsx @@ -1,6 +1,9 @@ import CategoryList from '@/components/review/FilterSection/CategoryList'; -import FilterList from './FiterList'; +import CloseDateToggle from './CloseDateToggle'; +import DateDropdown from './DateDropdown'; +import RegionDropdown from './RegionDropdown'; +import ReviewRating from '../ReviewRating'; interface FilterSectionProps { handleCategoryClick: (category: string) => void; @@ -9,13 +12,18 @@ interface FilterSectionProps { export default function FilterSection({ handleCategoryClick, selectedCategory }: FilterSectionProps) { return ( -
- {/* 카테고리 */} +
- + {/* 리뷰 별점 */} + {/* 필터 */} -
- +
+
+ + +
+ +
); diff --git a/src/components/review/ReviewRating/index.tsx b/src/components/review/ReviewRating/index.tsx new file mode 100644 index 00000000..1a5d0f3c --- /dev/null +++ b/src/components/review/ReviewRating/index.tsx @@ -0,0 +1,31 @@ +import { ProgressBar } from '@/components/shared/progress-bar'; +import Rating from '@/components/shared/Rating'; + +export default function ReviewRating() { + const SCORE = ( +
+

5점

+
+ +
+
+ ); + + return ( +
+
+ +

2.8

+ + +
+
+ {SCORE} + {SCORE} + {SCORE} + {SCORE} + {SCORE} +
+
+ ); +} diff --git a/src/pages/review/index.tsx b/src/pages/review/index.tsx index 8577251c..d3d54610 100644 --- a/src/pages/review/index.tsx +++ b/src/pages/review/index.tsx @@ -6,12 +6,12 @@ import RootLayout from '@/components/shared/RootLayout'; export default function Home() { const [searchValue, setSearchValue] = useState(''); - const [selectedCategory, setSelectedCategory] = useState('전체'); // 카테고리 + const [selectedCategory, setSelectedCategory] = useState('전체'); const handleCategoryClick = (category: string) => { - // 카테고리 클릭 setSelectedCategory(category); }; + return (
@@ -21,7 +21,6 @@ export default function Home() { {/* 카테고리 */} -
From 14521d56b7997578fd04d3787828027935c944ff Mon Sep 17 00:00:00 2001 From: inji0212 Date: Tue, 5 Nov 2024 15:21:21 +0900 Subject: [PATCH 03/35] =?UTF-8?q?feat=20=EB=A6=AC=EB=B7=B0=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EB=A6=AC=EB=B7=B0=EC=B9=B4=EB=93=9C=20?= =?UTF-8?q?=EC=A0=9C=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/review/FilterSection/index.tsx | 2 +- .../review/HeaderSection/Header/index.tsx | 27 ++++++ .../review/HeaderSection/SearchBar/index.tsx | 41 ++++++++++ src/components/review/HeaderSection/index.tsx | 18 ++++ .../review/ReviewCard/MoreButton/index.tsx | 21 +++++ src/components/review/ReviewCard/index.tsx | 82 +++++++++++++++++++ .../review/ReviewContainer/index.tsx | 3 + src/pages/review/index.tsx | 42 +++++++++- 8 files changed, 232 insertions(+), 4 deletions(-) create mode 100644 src/components/review/HeaderSection/Header/index.tsx create mode 100644 src/components/review/HeaderSection/SearchBar/index.tsx create mode 100644 src/components/review/HeaderSection/index.tsx create mode 100644 src/components/review/ReviewCard/MoreButton/index.tsx create mode 100644 src/components/review/ReviewCard/index.tsx create mode 100644 src/components/review/ReviewContainer/index.tsx diff --git a/src/components/review/FilterSection/index.tsx b/src/components/review/FilterSection/index.tsx index 461aae85..add1f636 100644 --- a/src/components/review/FilterSection/index.tsx +++ b/src/components/review/FilterSection/index.tsx @@ -12,7 +12,7 @@ interface FilterSectionProps { export default function FilterSection({ handleCategoryClick, selectedCategory }: FilterSectionProps) { return ( -
+
{/* 리뷰 별점 */} diff --git a/src/components/review/HeaderSection/Header/index.tsx b/src/components/review/HeaderSection/Header/index.tsx new file mode 100644 index 00000000..b77df90a --- /dev/null +++ b/src/components/review/HeaderSection/Header/index.tsx @@ -0,0 +1,27 @@ +import Image from 'next/image'; +import { FILTER_OPTIONS } from '@/constants/main/contants'; + +interface HeaderProps { + selectedCategory: string; +} + +export default function Header({ selectedCategory }: HeaderProps) { + return ( +
+ {FILTER_OPTIONS.map( + (option) => + selectedCategory === option.label && ( + 타이틀 로고 + ), + )} +

{selectedCategory}

+
+ ); +} diff --git a/src/components/review/HeaderSection/SearchBar/index.tsx b/src/components/review/HeaderSection/SearchBar/index.tsx new file mode 100644 index 00000000..7705aa28 --- /dev/null +++ b/src/components/review/HeaderSection/SearchBar/index.tsx @@ -0,0 +1,41 @@ +/* eslint-disable tailwindcss/no-custom-classname */ +import type { ChangeEvent, Dispatch, FormEvent, SetStateAction } from 'react'; +import Image from 'next/image'; + +interface SearchBarProps { + searchValue: string; + setSearchValue: Dispatch>; +} + +export default function SearchBar({ searchValue, setSearchValue }: SearchBarProps) { + const handleSearchChange = (e: ChangeEvent) => { + // 검색값 변화 확인 + setSearchValue(e.target.value); + // console.log('searchValue', searchValue); + }; + + const handleSearchSubmit = (e: FormEvent) => { + // 검색값 제출 + e.preventDefault(); + if (searchValue !== '') { + // console.log('제출성공'); + setSearchValue(''); + } + }; + + return ( +
+ + +
+ ); +} diff --git a/src/components/review/HeaderSection/index.tsx b/src/components/review/HeaderSection/index.tsx new file mode 100644 index 00000000..13d82481 --- /dev/null +++ b/src/components/review/HeaderSection/index.tsx @@ -0,0 +1,18 @@ +import type { Dispatch, SetStateAction } from 'react'; +import Header from '@/components/review/HeaderSection/Header'; +import SearchBar from '@/components/review/HeaderSection/SearchBar'; + +interface HeaderSectionProps { + searchValue: string; + selectedCategory: string; + setSearchValue: Dispatch>; +} + +export default function HeaderSection({ searchValue, setSearchValue, selectedCategory }: HeaderSectionProps) { + return ( +
+
+ +
+ ); +} diff --git a/src/components/review/ReviewCard/MoreButton/index.tsx b/src/components/review/ReviewCard/MoreButton/index.tsx new file mode 100644 index 00000000..4b2b99c8 --- /dev/null +++ b/src/components/review/ReviewCard/MoreButton/index.tsx @@ -0,0 +1,21 @@ +import Image from 'next/image'; + +type MoreButtonProps = { + isExpanded: boolean; + onClick: () => void; +}; + +export default function MoreButton({ isExpanded, onClick }: MoreButtonProps) { + return ( + + ); +} diff --git a/src/components/review/ReviewCard/index.tsx b/src/components/review/ReviewCard/index.tsx new file mode 100644 index 00000000..8cba6dfb --- /dev/null +++ b/src/components/review/ReviewCard/index.tsx @@ -0,0 +1,82 @@ +import { useEffect, useRef,useState } from 'react'; +import Image from 'next/image'; +import Rating from '@/components/shared/Rating'; + +import MoreButton from './MoreButton'; + +type ReviewList = { + ImagePath: string; + comment: string; + createdAt: string; + gatheringLocation: string; + gatheringName: string; + name: string; + profileImagePath: string; + score: number; + userId: string; +}; + +export function ReviewCard({ review }: { review: ReviewList }) { + const dateObj = new Date(review.createdAt); + const [isExpanded, setIsExpanded] = useState(false); + const [showMoreButton, setShowMoreButton] = useState(false); + const commentRef = useRef(null); + + const toggleExpand = () => setIsExpanded((prev) => !prev); + + const formattedDate = `${dateObj.getFullYear()}.${(dateObj.getMonth() + 1) + .toString() + .padStart(2, '0')}.${dateObj.getDate().toString().padStart(2, '0')}`; + + // 더보기 버튼 clamp-1에 따라 생기게 + const checkClamped = () => { + if (commentRef.current) { + const isClamped = commentRef.current.scrollHeight > commentRef.current.clientHeight; + setShowMoreButton(isClamped); + } + }; + useEffect(() => { + if (typeof window !== 'undefined') { + checkClamped(); + + window.addEventListener('resize', checkClamped); + + return () => window.removeEventListener('resize', checkClamped); + } + return undefined; + }, [review.comment]); + + return ( +
+ testImage +
+ +
+

+ {review.comment} + {!isExpanded && showMoreButton && '...'} +

+ {showMoreButton && ( + + )} +
+
+ {review.gatheringName} + + {review.gatheringLocation} +
+
+
+ profile +
+ {review.name} + | + {formattedDate} +
+
+
+ ); +} diff --git a/src/components/review/ReviewContainer/index.tsx b/src/components/review/ReviewContainer/index.tsx new file mode 100644 index 00000000..bf253c81 --- /dev/null +++ b/src/components/review/ReviewContainer/index.tsx @@ -0,0 +1,3 @@ +export default function ReviewContainer({ children }: { children: React.ReactNode }) { + return
{children}
; +} diff --git a/src/pages/review/index.tsx b/src/pages/review/index.tsx index d3d54610..6fdbdd53 100644 --- a/src/pages/review/index.tsx +++ b/src/pages/review/index.tsx @@ -1,12 +1,13 @@ import { useState } from 'react'; -import HeaderSection from '@/components/main/HeaderSection'; -import MainContainer from '@/components/main/MainContainer'; import FilterSection from '@/components/review/FilterSection'; +import HeaderSection from '@/components/review/HeaderSection'; +import { ReviewCard } from '@/components/review/ReviewCard'; +import MainContainer from '@/components/review/ReviewContainer'; import RootLayout from '@/components/shared/RootLayout'; export default function Home() { const [searchValue, setSearchValue] = useState(''); - const [selectedCategory, setSelectedCategory] = useState('전체'); + const [selectedCategory, setSelectedCategory] = useState('전체'); const handleCategoryClick = (category: string) => { setSelectedCategory(category); @@ -15,12 +16,47 @@ export default function Home() { return (
+
+

모든 리뷰

+ 만취를 이용한 분들은 이렇게 느꼈어요 +
{/* Header (타이틀, 검색창) */} {/* 카테고리 */} + +
+ + + +
From 7a33ea18d39b0e08259b3d56a4cafe7a9fe872f6 Mon Sep 17 00:00:00 2001 From: inji0212 Date: Wed, 6 Nov 2024 09:41:48 +0900 Subject: [PATCH 04/35] =?UTF-8?q?feat=20=EC=9E=84=EC=8B=9C=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=EB=84=A4=EC=9D=B4=EC=85=98=20=EB=B0=8F=20?= =?UTF-8?q?=EB=94=94=EC=9E=90=EC=9D=B8=20(=EB=94=94=EC=9E=90=EC=9D=B8=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EC=9C=BC=EB=A1=9C=20=EC=9D=B8=ED=95=9C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ReviewRating/index.tsx | 0 src/components/review/FilterSection/index.tsx | 6 +- src/components/review/ReviewCard/index.tsx | 45 ++++--- src/components/shared/pagination/index.tsx | 114 ++++++++++++++++++ src/pages/review/index.tsx | 88 +++++++++----- 5 files changed, 199 insertions(+), 54 deletions(-) rename src/components/review/{ => FilterSection}/ReviewRating/index.tsx (100%) create mode 100644 src/components/shared/pagination/index.tsx diff --git a/src/components/review/ReviewRating/index.tsx b/src/components/review/FilterSection/ReviewRating/index.tsx similarity index 100% rename from src/components/review/ReviewRating/index.tsx rename to src/components/review/FilterSection/ReviewRating/index.tsx diff --git a/src/components/review/FilterSection/index.tsx b/src/components/review/FilterSection/index.tsx index add1f636..16f4e20d 100644 --- a/src/components/review/FilterSection/index.tsx +++ b/src/components/review/FilterSection/index.tsx @@ -3,7 +3,6 @@ import CategoryList from '@/components/review/FilterSection/CategoryList'; import CloseDateToggle from './CloseDateToggle'; import DateDropdown from './DateDropdown'; import RegionDropdown from './RegionDropdown'; -import ReviewRating from '../ReviewRating'; interface FilterSectionProps { handleCategoryClick: (category: string) => void; @@ -14,10 +13,9 @@ export default function FilterSection({ handleCategoryClick, selectedCategory }: return (
- {/* 리뷰 별점 */} - + {/* 필터 */} -
+
diff --git a/src/components/review/ReviewCard/index.tsx b/src/components/review/ReviewCard/index.tsx index 8cba6dfb..bb129090 100644 --- a/src/components/review/ReviewCard/index.tsx +++ b/src/components/review/ReviewCard/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef,useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import Image from 'next/image'; import Rating from '@/components/shared/Rating'; @@ -23,13 +23,11 @@ export function ReviewCard({ review }: { review: ReviewList }) { const commentRef = useRef(null); const toggleExpand = () => setIsExpanded((prev) => !prev); - - const formattedDate = `${dateObj.getFullYear()}.${(dateObj.getMonth() + 1) - .toString() - .padStart(2, '0')}.${dateObj.getDate().toString().padStart(2, '0')}`; + + const formattedDate = `${dateObj.getFullYear()}.${(dateObj.getMonth() + 1).toString().padStart(2, '0')}.${dateObj.getDate().toString().padStart(2, '0')}`; // 더보기 버튼 clamp-1에 따라 생기게 - const checkClamped = () => { + const checkClamped = () => { if (commentRef.current) { const isClamped = commentRef.current.scrollHeight > commentRef.current.clientHeight; setShowMoreButton(isClamped); @@ -39,36 +37,43 @@ export function ReviewCard({ review }: { review: ReviewList }) { if (typeof window !== 'undefined') { checkClamped(); - window.addEventListener('resize', checkClamped); + const handleResize = () => { + checkClamped(); + setIsExpanded(false); + }; + + window.addEventListener('resize', handleResize); - return () => window.removeEventListener('resize', checkClamped); + return () => window.removeEventListener('resize', handleResize); } return undefined; }, [review.comment]); return ( -
- testImage -
+
+ testImage +
-
-

+

+

{review.comment} {!isExpanded && showMoreButton && '...'}

- {showMoreButton && ( - - )} + {showMoreButton && }
{review.gatheringName} {review.gatheringLocation}
-
+
profile
diff --git a/src/components/shared/pagination/index.tsx b/src/components/shared/pagination/index.tsx new file mode 100644 index 00000000..75d54045 --- /dev/null +++ b/src/components/shared/pagination/index.tsx @@ -0,0 +1,114 @@ +/* eslint-disable no-plusplus */ +import { useEffect, useState } from 'react'; +import Image from 'next/image'; + +type PaginationProps = { + page: number; + + setPage: (page: number) => void; + totalPage: number; +}; + +export default function Pagination({ page, totalPage, setPage }: PaginationProps) { + const [limit, setLimit] = useState(5); // 초기값을 모바일 사이즈로 설정 + const tabletLimit = 7; // 태블릿 사이즈에서 표시할 숫자 칸 수 + const mobileLimit = 5; // 모바일 사이즈에서 표시할 숫자 칸 수 + + // 화면 크기에 따라 표시할 숫자 칸 수 결정 + useEffect(() => { + const updateLimit = () => { + setLimit(window.innerWidth >= 768 ? tabletLimit : mobileLimit); + }; + + updateLimit(); // 초기값 설정 + + window.addEventListener('resize', updateLimit); // 화면 크기 변경 시 호출 + + return () => { + window.removeEventListener('resize', updateLimit); // 컴포넌트 언마운트 시 이벤트 리스너 제거 + }; + }, []); + + const getPageRange = () => { + const pageNumbers: (number | string)[] = []; + const halfLimit = Math.floor(limit / 2); + + // 시작 페이지 및 끝 페이지 결정 + let startPage = Math.max(1, page - halfLimit); + let endPage = Math.min(totalPage, page + halfLimit); + + // 전체 페이지 수가 표시할 수 있는 수보다 작을 경우 + if (totalPage <= limit) { + startPage = 1; + endPage = totalPage; + } + if (page <= halfLimit) { + endPage = limit; // 1부터 시작 + } else if (page + halfLimit >= totalPage) { + startPage = totalPage - limit + 1; // 끝에서부터 한정 + } + + // 페이지 번호 생성 + for (let i = startPage; i <= endPage; i++) { + pageNumbers.push(i); + } + + // 첫 페이지에 대한 '...' 추가 + if (startPage > 1) { + pageNumbers.unshift('...'); + pageNumbers.unshift(1); // 첫 페이지 추가 + } + + // 마지막 페이지에 대한 '...' 추가 + if (endPage < totalPage) { + pageNumbers.push('...'); + pageNumbers.push(totalPage); // 마지막 페이지 추가 + } + + return pageNumbers; + }; + + const handlePageChange = (newPage: number) => { + if (newPage >= 1 && newPage <= totalPage) { + setPage(newPage); + } + }; + + const pageNumbers = getPageRange(); // 페이지 번호 배열 가져오기 + + return ( +
+ {/* 이전 페이지 버튼 */} + + + {/* 페이지 번호 */} + {pageNumbers.map((pageNumber) => ( + + ))} + + {/* 다음 페이지 버튼 */} + +
+ ); +} diff --git a/src/pages/review/index.tsx b/src/pages/review/index.tsx index 6fdbdd53..736b291f 100644 --- a/src/pages/review/index.tsx +++ b/src/pages/review/index.tsx @@ -1,18 +1,60 @@ -import { useState } from 'react'; +/* eslint-disable @typescript-eslint/no-shadow */ +import { useEffect, useState } from 'react'; import FilterSection from '@/components/review/FilterSection'; import HeaderSection from '@/components/review/HeaderSection'; import { ReviewCard } from '@/components/review/ReviewCard'; import MainContainer from '@/components/review/ReviewContainer'; +import Pagination from '@/components/shared/pagination'; import RootLayout from '@/components/shared/RootLayout'; +type ReviewType = { + ImagePath: string; + comment: string; + createdAt: string; + gatheringLocation: string; + gatheringName: string; + name: string; + profileImagePath: string; + score: number; + userId: string; +}; +const mockData: ReviewType[] = Array.from({ length: 100 }, (_, i) => ({ + comment: `리뷰 내용 ${i + 1} - ${'123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890'}`, + createdAt: '2024-01-25', + name: `사용자 ${i + 1}`, + ImagePath: '/images/test-detail.png', + profileImagePath: '/images/profile.svg', + score: Math.floor(Math.random() * 5) + 1, + userId: `${i + 1}`, + gatheringName: '모르는 개 산책', + gatheringLocation: '이태원', +})); + export default function Home() { const [searchValue, setSearchValue] = useState(''); const [selectedCategory, setSelectedCategory] = useState('전체'); + const [data, setData] = useState([]); + const [page, setPage] = useState(0); + const [size, ] = useState(10); + const [totalPage, setTotalPage] = useState(0); const handleCategoryClick = (category: string) => { setSelectedCategory(category); }; + const fetchData = (page: number, size: number) => { + const start = page * size; + const end = start + size; + const slicedData = mockData.slice(start, end); + + setData(slicedData); + setTotalPage(Math.ceil(mockData.length / size)); + }; + + useEffect(() => { + fetchData(page, size); + }, [page, size]); + return (
@@ -26,36 +68,22 @@ export default function Home() { {/* 카테고리 */} - + {/* 카드 */}
- - - + {data.map((item) => ( +
+ +
+ ))} +
+ +
From 913330cee5f9b3b752d7ae65409bfc72de9a12d8 Mon Sep 17 00:00:00 2001 From: inji0212 Date: Wed, 6 Nov 2024 14:34:27 +0900 Subject: [PATCH 05/35] =?UTF-8?q?feat=201=EC=B0=A8=20api=EC=97=B0=EB=8F=99?= =?UTF-8?q?(=20=ED=8E=98=EC=9D=B4=EC=A7=80=EB=84=A4=EC=9D=B4=EC=85=98=20,?= =?UTF-8?q?=20sort=20x)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/getReviewData.ts | 47 +++++++ .../CategoryList/CategoryItems/index.tsx | 13 +- .../FilterSection/CategoryList/index.tsx | 11 +- .../FilterSection/CloseDateToggle/index.tsx | 21 +-- .../FilterSection/DateDropdown/index.tsx | 69 +++++++--- .../FilterSection/RegionDropdown/index.tsx | 46 +++++-- src/components/review/FilterSection/index.tsx | 72 ++++++++-- .../review/HeaderSection/Header/index.tsx | 8 +- .../review/HeaderSection/MainHeader/index.tsx | 8 ++ .../review/HeaderSection/SearchBar/index.tsx | 24 ++-- src/components/review/HeaderSection/index.tsx | 17 ++- src/components/review/ReviewCard/index.tsx | 53 +++---- src/pages/review/index.tsx | 129 ++++++++++++------ src/types/review.d.ts | 35 +++++ 14 files changed, 395 insertions(+), 158 deletions(-) create mode 100644 src/apis/getReviewData.ts create mode 100644 src/components/review/HeaderSection/MainHeader/index.tsx create mode 100644 src/types/review.d.ts diff --git a/src/apis/getReviewData.ts b/src/apis/getReviewData.ts new file mode 100644 index 00000000..6e3699d2 --- /dev/null +++ b/src/apis/getReviewData.ts @@ -0,0 +1,47 @@ +import instance from '@/apis/api'; +import type { GetReviewResponse } from '@manchui-api'; + +type GetReviewProps= { + category?: string; + endDate?: string; + location?: string; + page?: number; + query?: string; + size?: number; + sort?: string; + startDate?: string; +} + +export async function getReviewData({ + page = 0, + size = 10, + category, + location, + startDate, + endDate, + query, + sort, +}: GetReviewProps): Promise { + const params = new URLSearchParams({ + page: page.toString(), + size: size.toString(), + ...(sort && { sort }), + ...(query && { query }), + ...(category && { category }), + ...(location && { location }), + ...(startDate && { startDate }), + ...(endDate && { endDate }), + }); + + + try { + const res = await instance.get('/api/reviews?', { + params, + }); + + return res.data; + } catch (e) { + console.error('getReviewData 함수에서 오류 발생:', e); + throw new Error('리뷰 데이터를 불러오는데 실패했습니다.'); + } +} diff --git a/src/components/review/FilterSection/CategoryList/CategoryItems/index.tsx b/src/components/review/FilterSection/CategoryList/CategoryItems/index.tsx index 9fe024c7..e5ee0d4e 100644 --- a/src/components/review/FilterSection/CategoryList/CategoryItems/index.tsx +++ b/src/components/review/FilterSection/CategoryList/CategoryItems/index.tsx @@ -1,25 +1,24 @@ import Image from 'next/image'; interface CategoryItemsProps { - handleCategoryClick: (category: string) => void; + category?: string; + onCategoryClick: (category: string) => void; option: { icon: string; id: string; label: string }; - selectedCategory: string; } -export default function CategoryItems({ handleCategoryClick, selectedCategory, option }: CategoryItemsProps) { +export default function CategoryItems({ onCategoryClick, category, option }: CategoryItemsProps) { return (
{ - handleCategoryClick(option.label); + onCategoryClick(option.id); }} >