Skip to content

Commit 2446e6f

Browse files
committed
커뮤니티 게시글 목록 조회, 게시글 등록 API 연결
1 parent 09718a0 commit 2446e6f

File tree

3 files changed

+244
-156
lines changed

3 files changed

+244
-156
lines changed

src/components/community/Community.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ html {
379379
border-radius: 0;
380380
padding: 12px 0;
381381
border-bottom: 1px solid #eaeaea;
382-
382+
min-width: 720px;
383383
}
384384

385385
.post-card:first-child {

src/components/community/Community.jsx

Lines changed: 130 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -1,104 +1,91 @@
1-
import React from "react";
2-
import { useNavigate } from "react-router-dom";
1+
import React, { useEffect, useState } from "react";
2+
import {useNavigate} from "react-router-dom";
33
import "./Community.css";
44

5+
const API_BASE = "http://52.79.145.160:8080";
6+
57
export default function Community() {
68
const navigate = useNavigate();
79
const tabs = ["전체", "미해결", "해결됨"];
810
const filters = ["최신순", "정확도순", "답변많은순", "좋아요순"];
911

10-
const posts = [
11-
{
12-
status: "해결됨",
13-
title: "버블 정렬 시각화 프로젝트 공유합니다",
14-
summary: "버블 정렬 알고리즘을 시각적으로 이해하기 쉽게 구현해보았습니다. 피드백 부탁드려요!",
15-
tags: ["알고리즘", "정렬", "시각화"],
16-
author: "김코딩",
17-
date: "2023. 5. 15",
18-
likes: 24,
19-
comments: 8
20-
},
21-
{
22-
status: "해결됨",
23-
title: "그래프 탐색 알고리즘 비교: BFS vs DFS",
24-
summary: "그래프 탐색 알고리즘의 차이점을 비교하고 어떤 상황에서 더 적합한지 정리했습니다.",
25-
tags: ["그래프", "BFS", "DFS"],
26-
author: "이알고",
27-
date: "2023. 5. 17",
28-
likes: 32,
29-
comments: 12
30-
},
31-
{
32-
status: "해결됨",
33-
title: "동적 프로그래밍 문제 해결 가이드",
34-
summary: "DP 문제를 효율적으로 푸는 전략과 예제를 모아 정리해봤습니다.",
35-
tags: ["DP", "문제풀이", "코딩테스트"],
36-
author: "박코딩",
37-
date: "2023. 5. 18",
38-
likes: 40,
39-
comments: 15
40-
},
41-
{
42-
status: "해결됨",
43-
title: "수강 중 문의드립니다!",
44-
summary: "안녕하세요! 수업 강의 잘 듣고 있습니다! 궁금한 게 있어서 문의 남깁니다! numeric_only=True는 이번에 시험환경이 엄...",
45-
tags: ["python", "머신러닝", "빅데이터", "pandas", "빅데이터분석기사"],
46-
author: "lettig0555",
47-
date: "2시간 전",
48-
likes: 6,
49-
comments: 13
50-
},
51-
{
52-
status: "해결됨",
53-
title: "rmse 값 구하기",
54-
summary: "랜덤포레스트 후 rmse 값을 구할 때 이렇게 구해도 상관없을까요?? from sklearn.ensemble import RandomForest...",
55-
tags: ["python", "머신러닝", "빅데이터", "pandas", "빅데이터분석기사"],
56-
author: "민우",
57-
date: "6분 전",
58-
likes: 0,
59-
comments: 2
60-
},
61-
{
62-
status: "미해결",
63-
title: "2025년 1회 구조체와 연결리스트 문제누락",
64-
summary: "5페이지 구조체와 연결리스트 해설 누락된 것 같습니다.",
65-
tags: ["python", "java", "c", "정보처리기사"],
66-
author: "hyungjun jo",
67-
date: "7시간 전",
68-
likes: 0,
69-
comments: 3
70-
},
71-
{
72-
status: "해결됨",
73-
title: "작업형2, 작업형3 pd.get_dummies 시 drop_first 유무",
74-
summary: "작업형2 할때는 pd.get_dummies(df) 할때 drop_first가 들어가 있지 않았는데 작업형3 강의에서는 다중공선성 피해...",
75-
tags: ["python", "머신러닝", "빅데이터", "pandas", "빅데이터분석기사"],
76-
author: "섭식",
77-
date: "9시간 전",
78-
likes: 6,
79-
comments: 3
80-
},
81-
{
82-
status: "미해결",
83-
title: "궁금한게 있습니다!",
84-
summary: "학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! 질문글과 관련된 영상 위치를 알려주면 더 빠르게 답변드릴 수 있어요!",
85-
tags: ["python", "머신러닝", "빅데이터", "pandas", "빅데이터분석기사"],
86-
author: "eovnffppa",
87-
date: "9시간 전",
88-
likes: 0,
89-
comments: 15
90-
},
91-
{
92-
status: "해결됨",
93-
title: "안녕하세요 주말코딩님 질문드립니다",
94-
summary: "안녕하세요 주말코딩님. 저는 완전 문과 노베이스고 지금은 대기업 하청 전산실 OP로 일하면서 정보처리기사 실기 시험을...",
95-
tags: ["python", "정보처리기사"],
96-
author: "김재호",
97-
date: "10시간 전",
98-
likes: 0,
99-
comments: 10
100-
}
101-
];
12+
// ✅ 서버 데이터 상태
13+
const [posts, setPosts] = useState([]);
14+
const [loading, setLoading] = useState(true);
15+
const [error, setError] = useState("");
16+
17+
useEffect(() => {
18+
let ignore = false;
19+
const controller = new AbortController();
20+
21+
(async () => {
22+
try {
23+
setLoading(true);
24+
setError("");
25+
26+
const token = localStorage.getItem("token");
27+
if (!token) {
28+
// 🚨 비회원 접근 차단
29+
alert("로그인이 필요합니다.");
30+
navigate("/");
31+
return;
32+
}
33+
34+
const res = await fetch(`${API_BASE}/api/posts`, {
35+
method: "GET",
36+
headers: {
37+
Accept: "application/json",
38+
Authorization: `Bearer ${token}`,
39+
},
40+
signal: controller.signal,
41+
credentials: "include",
42+
});
43+
44+
if (!res.ok) {
45+
const text = await res.text();
46+
throw new Error(text || `목록 조회 실패 (${res.status})`);
47+
}
48+
49+
const data = await res.json(); // ← 배열 형태 가정
50+
if (ignore) return;
51+
52+
// ✅ createdAt(또는 id) 기준으로 최신 글이 먼저 오도록 정렬
53+
const getTime = (x) => (x ? Date.parse(x) || 0 : 0);
54+
const sorted = (Array.isArray(data) ? data : [])
55+
.slice()
56+
.sort((a, b) => {
57+
const diff = getTime(b.createdAt) - getTime(a.createdAt);
58+
if (diff !== 0) return diff;
59+
// createdAt이 같거나 비어있으면 id 내림차순으로 보정
60+
return (b.id ?? 0) - (a.id ?? 0);
61+
});
62+
63+
// 🔄 정렬된 목록을 UI 필드로 매핑
64+
const mapped = sorted.map((p) => ({
65+
id: p.id,
66+
status: p.status || "",
67+
title: p.title,
68+
summary: (p.content || "").replace(/<[^>]+>/g, "").slice(0, 120),
69+
tags: p.tags || [],
70+
author: p.writer || "익명",
71+
date: p.createdAt ? new Date(p.createdAt).toLocaleString() : "",
72+
likes: p.likeCount ?? 0,
73+
comments: p.commentCount ?? 0,
74+
}));
75+
76+
setPosts(mapped);
77+
} catch (e) {
78+
if (!ignore) setError(e.message || "알 수 없는 오류");
79+
} finally {
80+
if (!ignore) setLoading(false);
81+
}
82+
})();
83+
84+
return () => {
85+
ignore = true;
86+
controller.abort();
87+
};
88+
}, []);
10289

10390
return (
10491
<div className="community-wrapper">
@@ -155,42 +142,55 @@ export default function Community() {
155142
</button>
156143
</div>
157144

158-
<div className="post-list">
159-
{posts.map((post, i) => (
160-
<div
161-
key={i}
162-
className="post-card"
163-
onClick={() => navigate(`/community/post/${i}`)}
164-
style={{ cursor: "pointer" }}
165-
>
166-
<div className="post-meta">
167-
<div className="title-row">
168-
<span className={`badge ${post.status === "해결됨" ? "badge-solved" : ""}`}>
169-
{post.status}
170-
</span>
171-
<h3 className="post-title">{post.title}</h3>
145+
{/* ✅ 로딩/에러/빈 상태 */}
146+
{loading && <div className="post-list"><p>불러오는 중…</p></div>}
147+
{!loading && error && <div className="post-list"><p className="error">{error}</p></div>}
148+
{!loading && !error && posts.length === 0 && (
149+
<div className="post-list"><p>게시글이 없습니다.</p></div>
150+
)}
151+
152+
{!loading && !error && posts.length > 0 && (
153+
<div className="post-list">
154+
{posts.map((post) => (
155+
<div
156+
key={post.id}
157+
className="post-card"
158+
onClick={() => navigate(`/community/post/${post.id}`)} // ← id 사용
159+
style={{ cursor: "pointer" }}
160+
>
161+
<div className="post-meta">
162+
<div className="title-row">
163+
{/* 상태값 없으면 뱃지 숨김 */}
164+
{post.status ? (
165+
<span className={`badge ${post.status === "해결됨" ? "badge-solved" : ""}`}>
166+
{post.status}
167+
</span>
168+
) : null}
169+
<h3 className="post-title">{post.title}</h3>
170+
</div>
171+
<p className="post-summary">{post.summary}</p>
172172
</div>
173-
<p className="post-summary">{post.summary}</p>
174-
</div>
175-
<div className="post-tags">
176-
{post.tags.map((tag, j) => (
177-
<span key={j} className="tag">{tag}</span>
178-
))}
179-
</div>
180-
<div className="post-footer">
181-
<div className="post-footer-left">
182-
<span>{post.author}</span>
183-
<span>{post.date}</span>
173+
<div className="post-tags">
174+
{(post.tags || []).map((tag, j) => (
175+
<span key={j} className="tag">{tag}</span>
176+
))}
184177
</div>
185-
<div className="post-footer-right">
186-
<span>👍 {post.likes}</span>
187-
<span>💬 {post.comments}</span>
178+
<div className="post-footer">
179+
<div className="post-footer-left">
180+
<span>{post.author}</span>
181+
<span>{post.date}</span>
182+
</div>
183+
<div className="post-footer-right">
184+
<span>👍 {post.likes}</span>
185+
<span>💬 {post.comments}</span>
186+
</div>
188187
</div>
189188
</div>
190-
</div>
191-
))}
192-
</div>
189+
))}
190+
</div>
191+
)}
193192

193+
{/* 기존 페이징 UI는 유지 (서버 페이징 스펙 나오면 연결) */}
194194
<div className="pagination-wrapper">
195195
<div className="page-numbers">
196196
<button className="page-button active">1</button>
@@ -215,26 +215,11 @@ export default function Community() {
215215
<div className="popular-posts">
216216
<h4>주간 인기글</h4>
217217
<ul>
218-
<li>
219-
<div className="post-title">버블 정렬 시각화 프로젝트 공유합니다</div>
220-
<div className="post-author">김코딩</div>
221-
</li>
222-
<li>
223-
<div className="post-title">그래프 탐색 알고리즘 비교: BFS vs DFS</div>
224-
<div className="post-author">이알고</div>
225-
</li>
226-
<li>
227-
<div className="post-title">동적 프로그래밍 문제 해결 가이드</div>
228-
<div className="post-author">박코딩</div>
229-
</li>
230-
<li>
231-
<div className="post-title">백엔드 신입 CS 스터디 3기 모집</div>
232-
<div className="post-author">김지훈</div>
233-
</li>
234-
<li>
235-
<div className="post-title">AI 실전 활용을 위한 4주 집중 스터디, 애사모!</div>
236-
<div className="post-author">Edun</div>
237-
</li>
218+
<li><div className="post-title">버블 정렬 시각화 프로젝트 공유합니다</div><div className="post-author">김코딩</div></li>
219+
<li><div className="post-title">그래프 탐색 알고리즘 비교: BFS vs DFS</div><div className="post-author">이알고</div></li>
220+
<li><div className="post-title">동적 프로그래밍 문제 해결 가이드</div><div className="post-author">박코딩</div></li>
221+
<li><div className="post-title">백엔드 신입 CS 스터디 3기 모집</div><div className="post-author">김지훈</div></li>
222+
<li><div className="post-title">AI 실전 활용을 위한 4주 집중 스터디, 애사모!</div><div className="post-author">Edun</div></li>
238223
</ul>
239224
</div>
240225
</aside>

0 commit comments

Comments
 (0)