Skip to content

Commit

Permalink
Feat: 실제 API 연동 및 상태 관리, 페이지 컴포넌트 분리 및 파일 생성 (#28)
Browse files Browse the repository at this point in the history
- 전체 코드에서 실제 API 연동 작업 완료
- 혼합 접근법 활용 전역 상태 관리 방식 추가(jotai + react-query)
- 포스트 상세 조회 페이지 컴포넌트 구조 분리 및 리팩토링
- 환불 진행 및 환불 완료 페이지 파일명만 생성(빈 파일 상태)
  • Loading branch information
sunglitter committed Dec 9, 2024
1 parent 6f19ff6 commit f062bd2
Show file tree
Hide file tree
Showing 29 changed files with 1,882 additions and 1,670 deletions.
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
5 changes: 1 addition & 4 deletions src/api/axiosInstance.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import axios from 'axios';

const axiosInstance = axios.create({
baseURL: 'http://15.164.5.135:8080', // API URL
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
2 changes: 1 addition & 1 deletion src/components/common/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ const Header = () => {
</NavItem>
<NavItem>
<StyledLink
to={isAdmin ? '/adminpage' : '/mypage/setting'}
to={isAdmin ? '#' : '/mypage/setting'}
onClick={toggleMobileMenu}
>
{isAdmin ? 'Admin Page' : 'My Page'}
Expand Down
53 changes: 24 additions & 29 deletions src/components/common/PostList.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,33 @@
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';

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 +72,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 +85,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 +101,7 @@ const PostList: React.FC<PostListProps & { hideWriteButton?: boolean }> = ({
/>
</ActionsContainer>

{loading ? (
<NoPostMessage>게시물을 불러오는 중입니다...</NoPostMessage>
) : categoryFilteredPosts.length === 0 ? (
{categoryFilteredPosts.length === 0 ? (
<NoPostMessage>
선택된 카테고리에 해당하는 게시글이 없습니다.
</NoPostMessage>
Expand All @@ -124,7 +115,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={URL.createObjectURL(post.imageUrls[0])}
alt={`post.title`}
/>
<PostContent>
<PostTitle>{post.title}</PostTitle>
<PostDetails>
Expand Down Expand Up @@ -153,7 +147,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
33 changes: 20 additions & 13 deletions src/components/pages/admin/PostApprovalPage.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
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';

const PostApprovalPage = () => {
const location = useLocation();
Expand All @@ -23,7 +21,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 +47,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 +64,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 +109,7 @@ const PostApprovalPage = () => {

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

0 comments on commit f062bd2

Please sign in to comment.