Skip to content

Commit 2b84636

Browse files
committed
최신순/오래된순/좋아요순 추가
1 parent cbbce25 commit 2b84636

2 files changed

Lines changed: 111 additions & 35 deletions

File tree

src/components/community/Community.css

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,6 @@
3232
top: 96px;
3333
display: flex;
3434
flex-direction: column;
35-
gap: 20px;
36-
align-self: start;
37-
}
38-
39-
.sidebar-left h3 {
40-
margin: 0;
41-
font-size: 13px;
42-
text-transform: uppercase;
4335
letter-spacing: 0.1em;
4436
color: var(--community-muted);
4537
}
@@ -213,27 +205,41 @@
213205
gap: 16px;
214206
}
215207

208+
209+
216210
.filter-bar {
217211
display: flex;
218-
gap: 18px;
212+
gap: 12px;
219213
font-size: 15px;
220214
font-weight: 500;
221215
color: var(--community-muted);
216+
flex-wrap: wrap;
222217
}
223218

224-
.filter-bar button {
225-
background: none;
226-
border: none;
227-
padding: 8px 0;
228-
border-bottom: 2px solid transparent;
219+
.filter-chip {
220+
display: inline-flex;
221+
align-items: center;
222+
gap: 6px;
223+
padding: 8px 14px;
224+
border-radius: 999px;
225+
border: 1px solid transparent;
226+
background: var(--community-surface);
227+
color: var(--community-muted);
229228
cursor: pointer;
230-
color: inherit;
231-
transition: color 0.18s ease, border-color 0.18s ease;
229+
transition: background-color 0.18s ease, color 0.18s ease, border-color 0.18s ease, transform 0.18s ease;
232230
}
233231

234-
.filter-bar button.active {
232+
.filter-chip:hover {
233+
border-color: rgba(124, 92, 255, 0.35);
235234
color: var(--community-text);
236-
border-color: var(--community-accent);
235+
}
236+
237+
.filter-chip.active {
238+
background: rgba(124, 92, 255, 0.16);
239+
border-color: rgba(124, 92, 255, 0.52);
240+
color: var(--community-accent);
241+
font-weight: 600;
242+
transform: translateY(-1px);
237243
}
238244

239245
.write-btn {

src/components/community/Community.jsx

Lines changed: 87 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import config from "../../config";
66
export default function Community() {
77
const navigate = useNavigate();
88
const tabs = ["전체", "미해결", "해결됨"];
9-
const filters = ["최신순", "정확도순", "답변많은순", "좋아요순"];
9+
const filters = ["최신순", "오래된순", "좋아요순"];
1010

1111
// ✅ 서버 데이터 상태
1212
const [posts, setPosts] = useState([]);
@@ -17,6 +17,7 @@ export default function Community() {
1717
const [keyword, setKeyword] = useState("");
1818
const [tagKeyword, setTagKeyword] = useState([]);
1919
const [currentPage, setCurrentPage] = useState(1);
20+
const [lastKnownPage, setLastKnownPage] = useState(1);
2021

2122
const PAGE_SIZE = 10;
2223

@@ -65,24 +66,34 @@ export default function Community() {
6566
});
6667

6768
// 🔄 정렬된 목록을 UI 필드로 매핑
68-
const mapped = sorted.map((p) => ({
69-
id: p.id,
70-
status: p.status || "",
71-
title: p.title,
72-
summary: (p.content || "").replace(/<[^>]+>/g, "").slice(0, 120),
73-
tags: p.tags || [],
74-
author: p.writer || "익명",
75-
date: p.createdAt ? new Date(p.createdAt).toLocaleString() : "",
76-
likes: p.likeCount ?? 0,
77-
comments: p.commentCount ?? 0,
78-
}));
69+
const mapped = sorted.map((p) => {
70+
let createdAtMs = null;
71+
if (p.createdAt) {
72+
const parsed = new Date(p.createdAt).getTime();
73+
createdAtMs = Number.isFinite(parsed) ? parsed : null;
74+
}
75+
76+
return {
77+
id: p.id,
78+
status: p.status || "",
79+
title: p.title,
80+
summary: (p.content || "").replace(/<[^>]+>/g, "").slice(0, 120),
81+
tags: p.tags || [],
82+
author: p.writer || "익명",
83+
date: p.createdAt ? new Date(p.createdAt).toLocaleString() : "",
84+
createdAtMs,
85+
likes: p.likeCount ?? 0,
86+
comments: p.commentCount ?? 0,
87+
};
88+
});
7989

8090
setPosts(mapped);
8191
setKeyword("");
8292
setTagKeyword([]);
8393
setSearchInput("");
8494
setTagInput("");
8595
setCurrentPage(1);
96+
setLastKnownPage(1);
8697
} catch (e) {
8798
if (!ignore) setError(e.message || "알 수 없는 오류");
8899
} finally {
@@ -101,7 +112,7 @@ export default function Community() {
101112
return posts;
102113
}
103114

104-
const lowerKeyword = keyword.toLowerCase();
115+
const lowerKeyword = keyword.toLowerCase();
105116

106117
return posts.filter((post) => {
107118
const matchesKeyword = lowerKeyword
@@ -121,6 +132,46 @@ export default function Community() {
121132
});
122133
}, [keyword, tagKeyword, posts]);
123134

135+
const [activeFilter, setActiveFilter] = useState(filters[0]);
136+
137+
const sortedPosts = useMemo(() => {
138+
const sorted = [...filteredPosts];
139+
const ensureNumber = (value, fallback = null) => (Number.isFinite(value) ? value : fallback);
140+
141+
const latest = (a, b) => {
142+
const aTime = ensureNumber(a.createdAtMs, null);
143+
const bTime = ensureNumber(b.createdAtMs, null);
144+
if (aTime !== null && bTime !== null && aTime !== bTime) return bTime - aTime;
145+
if (bTime !== null) return 1;
146+
if (aTime !== null) return -1;
147+
return (b.id ?? 0) - (a.id ?? 0);
148+
};
149+
150+
const oldest = (a, b) => {
151+
const aTime = ensureNumber(a.createdAtMs, null);
152+
const bTime = ensureNumber(b.createdAtMs, null);
153+
if (aTime !== null && bTime !== null && aTime !== bTime) return aTime - bTime;
154+
if (aTime !== null) return -1;
155+
if (bTime !== null) return 1;
156+
return (a.id ?? 0) - (b.id ?? 0);
157+
};
158+
const byLikes = (a, b) => (b.likes ?? 0) - (a.likes ?? 0);
159+
160+
switch (activeFilter) {
161+
case "오래된순":
162+
sorted.sort(oldest);
163+
break;
164+
case "좋아요순":
165+
sorted.sort(byLikes);
166+
break;
167+
case "최신순":
168+
default:
169+
sorted.sort(latest);
170+
break;
171+
}
172+
return sorted;
173+
}, [filteredPosts, activeFilter]);
174+
124175
const handleSearchSubmit = (event) => {
125176
event?.preventDefault?.();
126177
const trimmedKeyword = searchInput.trim();
@@ -132,6 +183,7 @@ export default function Community() {
132183
setKeyword(trimmedKeyword);
133184
setTagKeyword(parsedTags);
134185
setCurrentPage(1);
186+
setLastKnownPage(1);
135187
};
136188

137189
const handleReset = () => {
@@ -140,6 +192,7 @@ export default function Community() {
140192
setKeyword("");
141193
setTagKeyword([]);
142194
setCurrentPage(1);
195+
setLastKnownPage(1);
143196
};
144197

145198
const totalPages = useMemo(() => {
@@ -150,7 +203,10 @@ export default function Community() {
150203
useEffect(() => {
151204
if (currentPage > totalPages) {
152205
setCurrentPage(totalPages);
206+
setLastKnownPage(totalPages);
207+
return;
153208
}
209+
setLastKnownPage(currentPage);
154210
}, [currentPage, totalPages]);
155211

156212
const pageNumbers = useMemo(() => (
@@ -160,8 +216,8 @@ export default function Community() {
160216
const paginatedPosts = useMemo(() => {
161217
const start = (currentPage - 1) * PAGE_SIZE;
162218
const end = start + PAGE_SIZE;
163-
return filteredPosts.slice(start, end);
164-
}, [filteredPosts, currentPage]);
219+
return sortedPosts.slice(start, end);
220+
}, [sortedPosts, currentPage]);
165221

166222
const jumpBy = 5;
167223
const goToPage = (page) => {
@@ -244,8 +300,22 @@ export default function Community() {
244300

245301
<div className="filter-area">
246302
<div className="filter-bar">
247-
{filters.map((filter, i) => (
248-
<button key={i} className={i === 0 ? "active" : ""}>{filter}</button>
303+
{filters.map((filter) => (
304+
<button
305+
key={filter}
306+
type="button"
307+
className={`filter-chip${filter === activeFilter ? " active" : ""}`}
308+
onClick={() => {
309+
setActiveFilter(filter);
310+
setCurrentPage((prev) => {
311+
const next = lastKnownPage;
312+
if (prev !== next) return next;
313+
return prev;
314+
});
315+
}}
316+
>
317+
{filter}
318+
</button>
249319
))}
250320
</div>
251321
<button className="write-btn" onClick={() => navigate("/community/write")}>

0 commit comments

Comments
 (0)