-
Notifications
You must be signed in to change notification settings - Fork 46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[이준희] sprint10 #304
The head ref may contain hidden characters: "Next-\uC774\uC900\uD76C-sprint10"
[이준희] sprint10 #304
Changes from 13 commits
eadc7a5
661338c
9fc48fd
31ac01a
e5789a5
902dbec
db4e0d8
1fed5e0
4ef94e7
1a4eabb
fdb4302
c407355
d8cdbf5
46f3f82
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,33 +18,27 @@ export default function AllArticles() { | |
const [isDropdown, setIsDropdown] = useState<boolean>(false); | ||
const [keyword, setKeyword] = useState<string>(""); | ||
|
||
const fetchArticles = async (reset: boolean = false) => { | ||
const fetchArticles = async (page: number = 1) => { | ||
try { | ||
if (isFetching) return; | ||
setIsFetching(true); | ||
|
||
const currentPage = reset ? 1 : page; | ||
|
||
const data = await getArticles({ | ||
orderBy: sortOrder, | ||
page: currentPage, | ||
page: page, | ||
pageSize: 10, | ||
keyword: keyword, | ||
}); | ||
|
||
if (reset) { | ||
if (page === 1) { | ||
setArticles(data.list); | ||
} else { | ||
if (currentPage === page) { | ||
const updatedArticles = [...articles, ...data.list]; | ||
setArticles(updatedArticles); | ||
} | ||
const updatedArticles = [...articles, ...data.list]; | ||
setArticles(updatedArticles); | ||
Comment on lines
+33
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이런 분기는 삼항연산자로도 표현 가능하긴 합니다. 취향에 맞게 사용하시면 될 것 같아요. |
||
} | ||
|
||
if (data.list.length < 10) { | ||
setHasMore(false); | ||
} else { | ||
setHasMore(true); | ||
} | ||
|
||
setIsFetching(false); | ||
|
@@ -54,12 +48,12 @@ export default function AllArticles() { | |
}; | ||
|
||
useEffect(() => { | ||
fetchArticles(true); | ||
fetchArticles(1); | ||
}, [sortOrder]); | ||
|
||
useEffect(() => { | ||
if (page === 1) return; | ||
fetchArticles(); | ||
fetchArticles(page); | ||
}, [page]); | ||
|
||
useEffect(() => { | ||
|
@@ -101,7 +95,8 @@ export default function AllArticles() { | |
|
||
const handleSearchSubmit = () => { | ||
setPage(1); | ||
fetchArticles(true); | ||
setHasMore(true); | ||
fetchArticles(1); | ||
}; | ||
|
||
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => { | ||
|
@@ -114,7 +109,9 @@ export default function AllArticles() { | |
<div className={styles.article_container}> | ||
<div className={styles.article_top}> | ||
<h2 className={styles.h2}>게시글</h2> | ||
<button className={styles.button}>글쓰기</button> | ||
<Link href={"/addboard"}> | ||
<button className={styles.button}>글쓰기</button> | ||
</Link> | ||
</div> | ||
|
||
<div className={styles.article_controls}> | ||
|
@@ -148,12 +145,11 @@ export default function AllArticles() { | |
{articles.map((article) => ( | ||
<Link | ||
key={article.id} | ||
href={`/articles/${article.id}`} | ||
href={`/boards/${article.id}`} | ||
className={styles.article_link} | ||
passHref | ||
> | ||
<div> | ||
{" "} | ||
<div className={styles.article_content}> | ||
<div className={styles.article_title}>{article.title}</div> | ||
<div className={styles.image_container}> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
import { useEffect, useState } from "react"; | ||
import ItemCard from "@/components/ItemCard"; | ||
import { getProducts } from "@/lib/api"; | ||
import Pagination from "@/components/Pagination"; | ||
import { Product } from "@/types/commontypes"; | ||
import styles from "@/styles/items.module.css"; | ||
import searchIcon from "@/public/svgs/ic_search.svg"; | ||
import Image from "next/image"; | ||
|
||
const getPageSize = () => { | ||
if (typeof window === "undefined") return 10; | ||
const width = window.innerWidth; | ||
if (width < 768) { | ||
return 4; | ||
} else if (width < 1280) { | ||
return 6; | ||
} else { | ||
return 10; | ||
} | ||
}; | ||
Comment on lines
+10
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 함수 다른 컴포넌트에서도 사용했던 것 같은데, 유틸 함수로 만들어서 꺼내쓰게 바꿔보시면 어떨까요? |
||
|
||
function AllItems() { | ||
const [orderBy, setOrderBy] = useState<string>("recent"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. useState의 타입에 'recent' | 'like' 이런식으로 타입을 특정 값으로 강제하는 방법도 추천드립니다. |
||
const [page, setPage] = useState<number>(1); | ||
const [pageSize, setPageSize] = useState<number>(getPageSize()); | ||
const [items, setItems] = useState<Product[]>([]); | ||
const [isDropdown, setIsDropdown] = useState<boolean>(false); | ||
const [totalPageNum, setTotalPageNum] = useState<number>(0); | ||
|
||
useEffect(() => { | ||
const handleFixSize = () => { | ||
setPageSize(getPageSize()); | ||
}; | ||
|
||
const fetchProducts = async ({ | ||
orderBy, | ||
page, | ||
pageSize, | ||
}: { | ||
orderBy: string; | ||
page: number; | ||
pageSize: number; | ||
}) => { | ||
const products = await getProducts({ orderBy, page, pageSize }); | ||
setItems(products.list); | ||
setTotalPageNum(Math.ceil(products.totalCount / pageSize)); | ||
}; | ||
|
||
window.addEventListener("resize", handleFixSize); | ||
fetchProducts({ orderBy, page, pageSize }); | ||
|
||
return () => { | ||
window.removeEventListener("resize", handleFixSize); | ||
}; | ||
}, [orderBy, page, pageSize]); | ||
|
||
const handleNextPage = (newPage: number) => { | ||
setPage(newPage); | ||
}; | ||
|
||
const toggleDropdown = () => { | ||
setIsDropdown(!isDropdown); | ||
}; | ||
|
||
const handleOrderByChange = (newOrderBy: string) => { | ||
setOrderBy(newOrderBy); | ||
setPage(1); | ||
setIsDropdown(false); | ||
}; | ||
|
||
return ( | ||
<div className={styles.all_item_container}> | ||
<div className={styles.all_item_content}> | ||
<div className={styles.all_item_header}> | ||
<div className={styles.all_item_header_front}> | ||
<div className={styles.all_item_title}>전체 상품</div> | ||
<div className={styles.all_item_search_container}> | ||
<Image | ||
className={styles.all_item_search_icon} | ||
src={searchIcon} | ||
alt="돋보기 아이콘" | ||
width={24} | ||
height={24} | ||
/> | ||
<input | ||
className={styles.all_item_search_input} | ||
placeholder="검색할 상품을 입력해주세요" | ||
/> | ||
</div> | ||
</div> | ||
<div className={styles.all_item_header_end}> | ||
<div className={styles.all_item_sort}> | ||
<a href="./additem"> | ||
<button className={styles.all_item_register_button}> | ||
상품 등록하기 | ||
</button> | ||
</a> | ||
<button | ||
className={styles.all_item_sort_button} | ||
onClick={toggleDropdown} | ||
> | ||
{orderBy === "recent" ? "최신순" : "좋아요순"} ▼ | ||
</button> | ||
{isDropdown && ( | ||
<div className={styles.all_item_sort_options}> | ||
<div onClick={() => handleOrderByChange("recent")}> | ||
최신순 | ||
</div> | ||
<div onClick={() => handleOrderByChange("favorite")}> | ||
좋아요순 | ||
</div> | ||
</div> | ||
)} | ||
</div> | ||
</div> | ||
</div> | ||
<div className={styles.all_item_card_container}> | ||
{items?.map((item) => ( | ||
<ItemCard item={item} key={`all_item_${item.id}`} /> | ||
))} | ||
</div> | ||
<Pagination | ||
currentPage={page} | ||
totalPageNum={totalPageNum} | ||
onPageChange={handleNextPage} | ||
/> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
export default AllItems; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import { useEffect, useState } from "react"; | ||
import { getProducts } from "@/lib/api"; | ||
import { Product, GetProductsResponse } from "@/types/commontypes"; | ||
import styles from "@/styles/items.module.css"; | ||
import BestItemCard from "./BestItemCard"; | ||
|
||
const getPageSize = () => { | ||
if (typeof window === "undefined") return 4; | ||
const width = window.innerWidth; | ||
if (width < 768) { | ||
return 1; | ||
} else if (width < 1280) { | ||
return 2; | ||
} else { | ||
return 4; | ||
} | ||
}; | ||
|
||
const debounce = (func: (...args: any[]) => void, delay: number) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. debounce 같은 함수도 자주 사용되기 때문에 유틸로 관리해주셔도 좋을 것 같습니다. |
||
let timeoutId: NodeJS.Timeout; | ||
return (...args: any[]) => { | ||
if (timeoutId) clearTimeout(timeoutId); | ||
timeoutId = setTimeout(() => { | ||
func(...args); | ||
}, delay); | ||
}; | ||
}; | ||
|
||
function BestItem() { | ||
const [items, setItems] = useState<Product[]>([]); | ||
const [pageSize, setPageSize] = useState<number>(getPageSize); | ||
|
||
const fetchProducts = async ({ | ||
orderBy, | ||
pageSize, | ||
}: { | ||
orderBy: string; | ||
pageSize: number; | ||
}) => { | ||
const products: GetProductsResponse = await getProducts({ | ||
orderBy, | ||
pageSize, | ||
}); | ||
setItems(products.list); | ||
}; | ||
|
||
useEffect(() => { | ||
const handleFixSize = debounce(() => { | ||
setPageSize(getPageSize()); | ||
}, 300); // 300ms 딜레이로 debounce 적용 | ||
|
||
window.addEventListener("resize", handleFixSize); | ||
fetchProducts({ orderBy: "favorite", pageSize }); | ||
|
||
return () => { | ||
window.removeEventListener("resize", handleFixSize); | ||
}; | ||
}, [pageSize]); | ||
|
||
return ( | ||
<div className={styles.best_item_container}> | ||
<div className={styles.best_item_content}> | ||
<div className={styles.best_item_title}>베스트 상품</div> | ||
<div className={styles.best_item_card_container}> | ||
{items?.map((item) => ( | ||
<BestItemCard item={item} key={`best-item-${item.id}`} /> | ||
))} | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
export default BestItem; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import Link from "next/link"; | ||
import { AllItemCardProps } from "@/types/commontypes"; | ||
import styles from "@/styles/bestitem.module.css"; | ||
import heartIcon from "@/public/svgs/ic_heart (1).svg"; | ||
import Image from "next/image"; | ||
|
||
function BestItemCard({ item }: AllItemCardProps) { | ||
return ( | ||
<div className={styles.item_card}> | ||
<img | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기는 next/image를 사용하지 않으신 이유가 있을까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
src={item.images[0]} | ||
alt={item.name} | ||
className={styles.item_card_img} | ||
/> | ||
|
||
<div className={styles.item_description}> | ||
<div className={styles.item_name}>{item.name}</div> | ||
<div className={styles.item_price}>{item.price.toLocaleString()}원</div> | ||
<div className={styles.item_favorite_count}> | ||
<Image src={heartIcon} alt="하트 아이콘" width={16} height={16} /> | ||
{item.favoriteCount} | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
export default BestItemCard; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
속성명과 값이 같은 경우 아래처럼 사용할 수 있습니다.