Skip to content
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

Feat: 실제 API 연동 및 혼합 접근법 활용 상태 관리 추가 #51

Merged
merged 6 commits into from
Dec 9, 2024
47 changes: 44 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"private": true,
"dependencies": {
"@reduxjs/toolkit": "^2.3.0",
"@tanstack/query-core": "^5.62.3",
"@tanstack/react-query": "^5.60.6",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
Expand All @@ -16,6 +17,8 @@
"@types/styled-components": "^5.1.34",
"@vitejs/plugin-react": "^4.3.3",
"axios": "^1.7.7",
"jotai": "^2.10.3",
"jotai-tanstack-query": "^0.9.0",
"react": "^18.3.1",
"react-daum-postcode": "^3.1.3",
"react-dom": "^18.3.1",
Expand Down
3 changes: 0 additions & 3 deletions src/api/axiosInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@ import axios from 'axios';
const axiosInstance = axios.create({
baseURL: import.meta.env.VITE_API_URL, // API URL
timeout: 5000, // 요청 제한 시간 (ms)
headers: {
'Content-Type': 'application/json', // JSON 형식 사용
},
});

// 요청/응답 인터셉터 설정 (선택 사항)
Expand Down
8 changes: 4 additions & 4 deletions src/components/common/CategoryWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const CategoryWrapper: React.FC<CategoryWrapperProps> = ({
{categories.map((category) => (
<CategoryItem
key={category.id}
active={category.id === currentCategory}
$active={category.id === currentCategory}
onClick={() => handleCategoryClick(category.id)}
>
{category.name}
Expand All @@ -65,15 +65,15 @@ const HeaderTitle = styled.h2`
white-space: nowrap; /* 텍스트 줄바꿈 방지 */
`;

const CategoryItem = styled.div<{ active: boolean }>`
const CategoryItem = styled.div<{ $active: boolean }>`
padding: 1rem 1rem;
font-weight: ${({ active }) => (active ? 'bold' : 'normal')};
font-weight: ${({ $active }) => ($active ? 'bold' : 'normal')};
cursor: pointer;
position: relative;

&::after {
content: '';
display: ${({ active }) => (active ? 'block' : 'none')};
display: ${({ $active }) => ($active ? 'block' : 'none')};
height: 2px;
background-color: black;
width: 100%;
Expand Down
20 changes: 14 additions & 6 deletions src/components/common/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ const Header = () => {
setIsMobileMenuOpen(!isMobileMenuOpen);
};

const handleCommunityClick = (
e: React.MouseEvent<HTMLAnchorElement, MouseEvent>
) => {
if (!isLoggedIn || !isAdmin) {
e.preventDefault();
alert('로그인 후 이용할 수 있는 페이지입니다.');
setIsMobileMenuOpen(!isMobileMenuOpen);
}
};

return (
<HeaderContainer>
<HeaderContent>
Expand Down Expand Up @@ -47,14 +57,14 @@ const Header = () => {
</StyledLink>
</NavItem>
<NavItem>
<StyledLink to="/community/post" onClick={toggleMobileMenu}>
<StyledLink to="/community/post" onClick={handleCommunityClick}>
Community
</StyledLink>
</NavItem>
<NavItem>
{isLoggedIn && (
<StyledLink
to={isAdmin ? '/adminpage' : '/mypage/setting'}
to={isAdmin ? '#' : '/mypage/setting'}
onClick={toggleMobileMenu}
>
{isAdmin ? 'Admin Page' : 'My Page'}
Expand All @@ -63,12 +73,10 @@ const Header = () => {
{isAdmin && (
<SubMenu>
<SubMenuItem>
<StyledLink to="/adminpage/post-management">
Post Management
</StyledLink>
<StyledLink to="/admin/post">Post Management</StyledLink>
</SubMenuItem>
<SubMenuItem>
<StyledLink to="/adminpage/chat-management">
<StyledLink to="/admin/chatlist">
Chat Management
</StyledLink>
</SubMenuItem>
Expand Down
54 changes: 25 additions & 29 deletions src/components/common/PostList.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,34 @@
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import { getPosts, Post } from '../pages/community/api/postApi';
import WriteButton from './WriteButton';
import SearchBar from './SearchBar';
import Pagination from './Pagination';
import { useNavigate } from 'react-router-dom';
import { SSEEvent, Post } from '../../types/postTypes';
import { getImageSrc } from '../../hooks/GetImageSrc';

interface PostListProps {
posts: Post[];
selectedCategory: string;
posts: Post[];
onPostSelect?: (postId: number) => void; // onPostSelect 추가
realTimeData: Record<number, SSEEvent>; // 실시간 데이터 매핑 추가
}

const POSTS_PER_PAGE = 6; // 한 페이지에 표시할 게시글 수

const PostList: React.FC<PostListProps & { hideWriteButton?: boolean }> = ({
selectedCategory,
posts,
realTimeData,
onPostSelect,
hideWriteButton,
}) => {
const navigate = useNavigate();
const [posts, setPosts] = useState<Post[]>([]); // 실제 게시글 데이터
const [currentPage, setCurrentPage] = useState(1);
const [searchTerm, setSearchTerm] = useState(''); // 입력된 검색어
const [searchQuery, setSearchQuery] = useState(''); // 실제 검색 실행 시의 검색어
const [loading, setLoading] = useState(false); // 로딩 상태

// 게시글 데이터 로드
const loadPosts = async () => {
try {
setLoading(true);
const data = await getPosts(selectedCategory, searchQuery); // API 호출
setPosts(data);
} catch (error) {
console.error('게시물 조회 중 오류 발생:', error);
alert('게시물 조회 중 오류가 발생했습니다. 다시 시도해주세요.');
} finally {
setLoading(false);
}
};

// 카테고리나 검색어 변경 시 데이터 로드
useEffect(() => {
loadPosts();
}, [selectedCategory, searchQuery]);

// 선택된 카테고리에 따른 게시글 필터링 (status 조건 추가)
// 선택된 카테고리에 따른 게시글 필터링
const categoryFilteredPosts = posts
.filter((post) => {
if (selectedCategory === 'NOT_APPROVED') {
Expand Down Expand Up @@ -88,6 +73,10 @@ const PostList: React.FC<PostListProps & { hideWriteButton?: boolean }> = ({

// 포스트 클릭 핸들러
const handlePostClick = (communityPostId: number) => {
if (onPostSelect) {
onPostSelect(communityPostId); // 부모 컴포넌트에 선택 이벤트 전달
}

if (selectedCategory === 'NOT_APPROVED') {
navigate(`/admin/post/approval/${communityPostId}`, {
state: { communityPostId },
Expand All @@ -97,6 +86,11 @@ const PostList: React.FC<PostListProps & { hideWriteButton?: boolean }> = ({
}
};

// 특정 포스트의 현재 참여 수 계산
const getParticipationCount = (postId: number): number => {
return realTimeData[postId]?.participationCount || 0;
};

return (
<Container>
<ActionsContainer>
Expand All @@ -108,9 +102,7 @@ const PostList: React.FC<PostListProps & { hideWriteButton?: boolean }> = ({
/>
</ActionsContainer>

{loading ? (
<NoPostMessage>게시물을 불러오는 중입니다...</NoPostMessage>
) : categoryFilteredPosts.length === 0 ? (
{categoryFilteredPosts.length === 0 ? (
<NoPostMessage>
선택된 카테고리에 해당하는 게시글이 없습니다.
</NoPostMessage>
Expand All @@ -124,7 +116,10 @@ const PostList: React.FC<PostListProps & { hideWriteButton?: boolean }> = ({
key={post.communityPostId}
onClick={() => handlePostClick(post.communityPostId)}
>
<PostImage src={post.imageUrls[0]} alt={post.title} />
<PostImage
src={getImageSrc(post.imageUrls[0])}
alt={`post.title`}
/>
<PostContent>
<PostTitle>{post.title}</PostTitle>
<PostDetails>
Expand Down Expand Up @@ -153,7 +148,8 @@ const PostList: React.FC<PostListProps & { hideWriteButton?: boolean }> = ({
</PostCloseAt>
</PostDate>
<PostJoinStatus>
참여 현황: {post.currentQuantity} / {post.availableNumber}
참여 현황: {getParticipationCount(post.communityPostId)} /{' '}
{post.availableNumber}
</PostJoinStatus>
</PostDetails>
<PostDescription>{post.description}</PostDescription>
Expand Down
4 changes: 2 additions & 2 deletions src/components/pages/admin/ChatRoomManagementPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import styled from 'styled-components';
import { useLocation, useNavigate } from 'react-router-dom';
import ChatRoomModal from '../community/modal/ChatRoomModal'; // 기존 채팅 모달 컴포넌트
import Pagination from '../../common/Pagination'; // 기존 페이지네이션 컴포넌트
import { fetchChatRooms } from '../community/api/chatApi';
import { fetchAllChatRooms } from '../community/api/chatApi';
import { FaBackspace } from 'react-icons/fa';
import { webSocketService } from '../../../utils/webSocket';

Expand Down Expand Up @@ -32,7 +32,7 @@ const ChatRoomManagementPage = () => {
useEffect(() => {
const fetchData = async () => {
try {
const rooms = await fetchChatRooms();
const rooms = await fetchAllChatRooms();
setChatRooms(rooms);
} catch (error) {
console.error('Failed to fetch chat rooms:', error);
Expand Down
34 changes: 21 additions & 13 deletions src/components/pages/admin/PostApprovalPage.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import { useNavigate, useLocation } from 'react-router-dom';
import {
Post,
updatePostStatus,
fetchPostById,
} from '../community/api/postApi';
import { fetchPostById } from '../community/api/postApi';
import { approvePost, rejectPost } from './api/adminApi';
import { FaBackspace, FaAngleLeft, FaAngleRight } from 'react-icons/fa';
import { Post } from '../../../types/postTypes';
import { getImageSrc } from '../../../hooks/GetImageSrc';

const PostApprovalPage = () => {
const location = useLocation();
Expand All @@ -23,7 +22,7 @@ const PostApprovalPage = () => {
}
try {
const postDetails = await fetchPostById(postId); // 포스트 세부 정보 가져오기
setPost(postDetails);
setPost(postDetails.communityPost);
} catch (error) {
console.error('Failed to fetch post details:', error);
}
Expand All @@ -49,9 +48,14 @@ const PostApprovalPage = () => {
const handleApprove = async () => {
if (!post) return;
try {
await updatePostStatus(postId, 'APPROVED'); // 포스트 상태를 APPROVED로 변경
// 제목에서 '(수정요망)' 제거
const updatedTitle = post.title.startsWith('(수정요망)')
? post.title.replace(/^\(수정요망\)\s*/, '')
: post.title;

await approvePost(postId, updatedTitle); // 포스트 상태를 APPROVED로 변경
alert('게시물이 승인되었습니다.');
navigate('/admin/post'); // 승인 후 관리자 페이지로 리다이렉트
navigate('/admin/posts'); // 승인 후 관리자 페이지로 리다이렉트
} catch (error) {
console.error('Failed to approve post:', error);
alert('승인 처리 중 오류가 발생했습니다.');
Expand All @@ -61,9 +65,14 @@ const PostApprovalPage = () => {
const handleReject = async () => {
if (!post) return;
try {
await updatePostStatus(postId, 'REJECTED'); // 포스트 상태를 REJECTED로 변경하고 제목 수정
// 제목에 '(수정요망)' 추가
const updatedTitle = post.title.startsWith('(수정요망)')
? post.title // 이미 '(수정요망)'이 있으면 그대로 유지
: `(수정요망) ${post.title}`;

await rejectPost(postId, updatedTitle); // 포스트 상태를 REJECTED로 변경
alert('게시물이 거절 처리되었습니다.');
navigate('/admin/post'); // 거절 후 관리자 페이지로 리다이렉트
navigate('/admin/posts'); // 거절 후 관리자 페이지로 리다이렉트
} catch (error) {
console.error('Failed to reject post:', error);
alert('거절 처리 중 오류가 발생했습니다.');
Expand Down Expand Up @@ -101,7 +110,7 @@ const PostApprovalPage = () => {

<ImagePreview>
<img
src={post.imageUrls[currentIndex]}
src={getImageSrc(post.imageUrls[currentIndex])}
alt={`이미지 ${currentIndex + 1}`}
/>
</ImagePreview>
Expand Down Expand Up @@ -161,8 +170,7 @@ const PostApprovalPage = () => {
<Label>카테고리</Label> {post.category}
</Detail>
<Detail>
<Label>참여 현황</Label> {post.currentQuantity}
{' / '}
<Label>참여 필요 수량</Label>
{post.availableNumber}
</Detail>
</DoubleWrapper>
Expand Down
Loading