Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 56 additions & 10 deletions src/domains/community/api/fetchPost.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { getApi } from '@/app/api/config/appConfig';
import { Post } from '@/domains/community/types/post';
import { ParamValue } from 'next/dist/server/request/params';
import { tabItem } from '../main/CommunityTab';

export const fetchPost = async (): Promise<Post[] | null> => {
export const fetchPost = async (lastId?: number | null): Promise<Post[] | null> => {
try {
const res = await fetch(`${getApi}/posts?postSortStatus=LATEST`, {
method: 'GET',
cache: 'no-store',
});
const res = await fetch(
`${getApi}/posts?${lastId ? `lastId=${lastId}&` : ''}postSortStatus=LATEST`,
{
method: 'GET',
cache: 'no-store',
}
);
const data = await res.json();
return data.data;
} catch (err) {
Expand All @@ -29,15 +33,57 @@ export const fetchPostById = async (postId: ParamValue) => {
}
};

export const fetchPostByTab = async (selectedTab: string): Promise<Post[] | null> => {
export const fetchPostByTab = async ({
category,
filter = 'LATEST',
lastId,
lastLikeCount,
lastCommentCount,
}: {
category?: string;
filter?: 'LATEST' | 'POPULAR' | 'COMMENTS';
lastId?: number;
lastLikeCount?: number | null;
lastCommentCount?: number | null;
}): Promise<Post[] | null> => {
try {
const data = await fetchPost();
const params = new URLSearchParams();

if (category && category !== 'all') {
const categoryId = tabItem.findIndex((tab) => tab.key === category);
if (categoryId >= 0) {
params.set('categoryId', categoryId.toString());
}
}

if (lastId) params.set('lastId', lastId.toString());

switch (filter) {
case 'POPULAR':
if (lastLikeCount) params.set('lastLikeCount', lastLikeCount.toString());
params.set('postSortStatus', 'POPULAR');
break;
case 'COMMENTS':
if (lastCommentCount) params.set('lastCommentCount', lastCommentCount.toString());
params.set('postSortStatus', 'COMMENTS');
break;
case 'LATEST':
default:
params.set('postSortStatus', 'LATEST');
break;
}

const res = await fetch(`${getApi}/posts?${params.toString()}`, {
method: 'GET',
cache: 'no-store',
});

const data = await res.json();
if (!data) return null;

const filtered = data.filter((post) => post.categoryName === selectedTab);
return filtered;
return data.data; // ํ•„์š”ํ•˜๋‹ค๋ฉด filter ์ถ”๊ฐ€ ๊ฐ€๋Šฅ
} catch (err) {
console.error('๊ธ€ ๋ชฉ๋ก ํ•„ํ„ฐ๋ง ์‹คํŒจ', err);
console.error('๊ธ€ ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ ์‹คํŒจ', err);
return null;
}
};
108 changes: 94 additions & 14 deletions src/domains/community/main/Community.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,121 @@
'use client';

import { useEffect, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import CommunityFilter from './CommunityFilter';
import CommunityTab from './CommunityTab';
import PostCard from './PostCard';
import WriteBtn from './WriteBtn';
import { Post } from '../types/post';
import { fetchPost } from '../api/fetchPost';
import { fetchPostByTab } from '../api/fetchPost';
import { useSearchParams } from 'next/navigation';

function Community() {
const [posts, setPosts] = useState<Post[]>([]);
const [posts, setPosts] = useState<Post[] | null>([]);
const [isLoading, setIsLoading] = useState(true);
const [lastLoadedId, setLastLoadedId] = useState<number | null>(null);

const searchParams = useSearchParams();

const category = useMemo(() => searchParams.get('category') || 'all', [searchParams]);
const filter = useMemo(
() => (searchParams.get('postSortStatus') as 'LATEST' | 'POPULAR' | 'COMMENTS') || 'LATEST',
[searchParams]
);

const [isEnd, setIsEnd] = useState(false);

useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
const data = await fetchPost();
if (!data) return;
setPosts(data);
setPosts([]);
setIsEnd(false);
setLastLoadedId(null);
loadInitialPosts();
}, [category, filter]);

const loadInitialPosts = async () => {
const category = searchParams.get('category') || 'all';
const filter =
(searchParams.get('postSortStatus') as 'LATEST' | 'POPULAR' | 'COMMENTS') || 'LATEST';

setIsLoading(true);
setIsEnd(false);

const lastLikeCount =
posts && posts.length > 0 ? Math.min(...posts.map((post) => post.likeCount)) : null;

const lastCommentCount =
posts && posts.length > 0 ? Math.min(...posts.map((post) => post.commentCount)) : null;

try {
const newPosts = await fetchPostByTab({
category,
filter,
lastLikeCount,
lastCommentCount,
});

if (!newPosts || newPosts.length === 0) {
setIsEnd(true);
setPosts([]);
} else {
setPosts(newPosts);
}
} finally {
setIsLoading(false);
}
};

const loadMorePosts = async (lastPostId: number) => {
if (isEnd || isLoading) return;
if (!posts || posts.length === 0) return;
console.log('์‹œ์ž‘', lastPostId);

const lastPost = posts[posts.length - 1];
if (lastPostId === lastPost.postId) return;
setLastLoadedId(lastPost.postId);

setIsLoading(true);
try {
const category = searchParams.get('category') || 'all';
const filter =
(searchParams.get('postSortStatus') as 'LATEST' | 'POPULAR' | 'COMMENTS') || 'LATEST';

const newPosts = await fetchPostByTab({
category,
filter,
lastId: lastPostId,
});

if (!newPosts || newPosts?.length === 0) {
setIsEnd(true);
console.log('๋');
} else {
setPosts((prev) => [...(prev ?? []), ...(newPosts ?? [])]);
}
} finally {
setIsLoading(false);
};
fetchData();
}, [setPosts]);
}
};

return (
<>
<section
aria-label="ํƒญ๊ณผ ๊ธ€์“ฐ๊ธฐ"
className="flex justify-between item-center sm:flex-row flex-col gap-4 mt-1"
>
<CommunityTab setPosts={setPosts} />
<CommunityTab setPosts={setPosts} setIsLoading={setIsLoading} setIsEnd={setIsEnd} />
<WriteBtn />
</section>

<section aria-label="๊ฒŒ์‹œ๋ฌผ ๋ชฉ๋ก">
<CommunityFilter posts={posts} />
<PostCard posts={posts} isLoading={isLoading} />
<CommunityFilter posts={posts} setPosts={setPosts} />
<PostCard
posts={posts}
setPost={setPosts}
isLoading={isLoading}
setIsLoading={setIsLoading}
isEnd={isEnd}
onLoadMore={loadMorePosts}
/>
</section>
</>
);
Expand Down
48 changes: 44 additions & 4 deletions src/domains/community/main/CommunityFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,59 @@

import { Post } from '../types/post';
import SelectBox from '@/shared/components/select-box/SelectBox';
import { Dispatch, SetStateAction, useEffect } from 'react';
import { fetchPostByTab } from '../api/fetchPost';
import { useRouter, useSearchParams } from 'next/navigation';

type Props = {
posts: Post[];
posts: Post[] | null;
setPosts: Dispatch<SetStateAction<Post[] | null>>;
};

function CommunityFilter({ posts }: Props) {
const sortMap = {
์ตœ์‹ ์ˆœ: 'LATEST',
์ธ๊ธฐ์ˆœ: 'POPULAR',
๋Œ“๊ธ€์ˆœ: 'COMMENTS',
} as const;

function CommunityFilter({ posts, setPosts }: Props) {
const searchParams = useSearchParams();
const query = searchParams.get('category');
const router = useRouter();

useEffect(() => {
console.log(query);
}, [query]);

const handleChange = async (selectTitle: string) => {
if (!query) return;

console.log(selectTitle);

const data = await fetchPostByTab({
category: query,
filter: sortMap[selectTitle as keyof typeof sortMap],
});
if (!data) return;
setPosts(data);
};

return (
<section
className="w-full flex justify-between items-center border-b-1 border-gray-light pb-1.5"
aria-label="์ปค๋ฎค๋‹ˆํ‹ฐ ์ •๋ ฌ ํ•„ํ„ฐ"
>
<p aria-live="polite">{posts.length}๊ฐœ</p>
<SelectBox option={['์ตœ์‹ ์ˆœ', '์ธ๊ธฐ์ˆœ', '๋Œ“๊ธ€์ˆœ']} title={'์ตœ์‹ ์ˆœ'} />
<p aria-live="polite">{posts && posts.length}๊ฐœ</p>
<SelectBox
option={['์ตœ์‹ ์ˆœ', '์ธ๊ธฐ์ˆœ', '๋Œ“๊ธ€์ˆœ']}
title={'์ตœ์‹ ์ˆœ'}
onChange={(value) => {
const sortValue = sortMap[value as keyof typeof sortMap];

handleChange(value);
router.push(`?category=${query || '์ „์ฒด'}&postSortStatus=${sortValue}`);
}}
/>
</section>
);
}
Expand Down
64 changes: 34 additions & 30 deletions src/domains/community/main/CommunityTab.tsx
Original file line number Diff line number Diff line change
@@ -1,56 +1,60 @@
'use client';

import tw from '@/shared/utills/tw';
import { useEffect, useState } from 'react';
import { fetchPost, fetchPostByTab } from '../api/fetchPost';
import { Post } from '../types/post';
import { useState } from 'react';
import { fetchPost, fetchPostByTab } from '../api/fetchPost';
import { useRouter, useSearchParams } from 'next/navigation';

type Props = {
setPosts: (value: Post[]) => void;
setPosts: (value: Post[] | null) => void;
setIsLoading: (value: boolean) => void;
setIsEnd: (value: boolean) => void;
};

const tabItem = [
{ title: '์ „์ฒด' },
{ title: '๋ ˆ์‹œํ”ผ' },
{ title: 'ํŒ' },
{ title: '์งˆ๋ฌธ' },
{ title: '์ž์œ ' },
export const tabItem = [
{ key: 'all', label: '์ „์ฒด' },
{ key: 'recipe', label: '๋ ˆ์‹œํ”ผ' },
{ key: 'tip', label: 'ํŒ' },
{ key: 'question', label: '์งˆ๋ฌธ' },
{ key: 'chat', label: '์ž์œ ' },
];

function CommunityTab({ setPosts }: Props) {
const [selectedIdx, setSelectedIdx] = useState(0);

useEffect(() => {
const fetchData = async () => {
const selectedTab = tabItem[selectedIdx].title;
function CommunityTab({ setPosts, setIsLoading, setIsEnd }: Props) {
const searchParams = useSearchParams();
const router = useRouter();

let data;
if (selectedTab === '์ „์ฒด') data = await fetchPost();
else data = await fetchPostByTab(selectedTab);
const currentSort = searchParams.get('postSortStatus') || 'LATEST';

if (!data) return;
setPosts(data);
};
fetchData();
}, [selectedIdx, setPosts]);
const [selectedCategory, setSelectedCategory] = useState(() => {
const param = searchParams.get('category') || 'all';
const exists = tabItem.some(({ key }) => key === param);
return exists ? param : 'all';
});

return (
<section className="relative sm:w-[70%] w-full" aria-label="์ปค๋ฎค๋‹ˆํ‹ฐ ํƒญ">
<div className="w-full overflow-x-scroll no-scrollbar scroll-smooth">
<div className="flex gap-3 w-max" aria-label="์ปค๋ฎค๋‹ˆํ‹ฐ ์นดํ…Œ๊ณ ๋ฆฌ">
{tabItem.map(({ title }, idx) => (
{tabItem.map(({ key, label }) => (
<button
key={title + idx}
key={key}
role="tab"
aria-selected={selectedIdx === idx}
tabIndex={selectedIdx === idx ? 0 : -1}
onClick={() => setSelectedIdx(idx)}
aria-selected={selectedCategory === key}
tabIndex={selectedCategory === key ? 0 : -1}
onClick={() => {
setSelectedCategory(key);
const params = new URLSearchParams();
params.set('category', key);
params.set('postSortStatus', currentSort); // โœ… ํ˜„์žฌ ํ•„ํ„ฐ ์ƒํƒœ ์œ ์ง€
router.push(`?${params.toString()}`);
}}
className={tw(
`border-1 py-1 px-3 rounded-2xl transition-colors ease-in min-w-18`,
selectedIdx === idx ? 'bg-secondary text-primary' : 'hover:bg-secondary/20'
selectedCategory === key ? 'bg-secondary text-primary' : 'hover:bg-secondary/20'
)}
>
{title}
{label}
</button>
))}
</div>
Expand Down
Loading