2025-10-09 16:31:46 +09:00
|
|
|
"use client";
|
|
|
|
|
import useSWRInfinite from "swr/infinite";
|
|
|
|
|
|
|
|
|
|
type Item = {
|
|
|
|
|
id: string;
|
|
|
|
|
title: string;
|
|
|
|
|
createdAt: string;
|
|
|
|
|
isPinned: boolean;
|
|
|
|
|
status: string;
|
|
|
|
|
stat?: { recommendCount: number; views: number; commentsCount: number } | null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type Resp = {
|
|
|
|
|
total: number;
|
|
|
|
|
page: number;
|
|
|
|
|
pageSize: number;
|
|
|
|
|
items: Item[];
|
|
|
|
|
sort: "recent" | "popular";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const fetcher = (url: string) => fetch(url).then((r) => r.json());
|
|
|
|
|
|
2025-10-09 16:54:24 +09:00
|
|
|
export function PostList({ boardId, sort = "recent", q, tag, author, start, end }: { boardId?: string; sort?: "recent" | "popular"; q?: string; tag?: string; author?: string; start?: string; end?: string }) {
|
2025-10-09 16:31:46 +09:00
|
|
|
const pageSize = 10;
|
|
|
|
|
const getKey = (index: number, prev: Resp | null) => {
|
|
|
|
|
if (prev && prev.items.length === 0) return null;
|
|
|
|
|
const page = index + 1;
|
|
|
|
|
const sp = new URLSearchParams({ page: String(page), pageSize: String(pageSize), sort });
|
|
|
|
|
if (boardId) sp.set("boardId", boardId);
|
2025-10-09 16:35:19 +09:00
|
|
|
if (q) sp.set("q", q);
|
2025-10-09 16:54:24 +09:00
|
|
|
if (tag) sp.set("tag", tag);
|
|
|
|
|
if (author) sp.set("author", author);
|
|
|
|
|
if (start) sp.set("start", start);
|
|
|
|
|
if (end) sp.set("end", end);
|
2025-10-09 16:31:46 +09:00
|
|
|
return `/api/posts?${sp.toString()}`;
|
|
|
|
|
};
|
|
|
|
|
const { data, size, setSize, isLoading } = useSWRInfinite<Resp>(getKey, fetcher);
|
|
|
|
|
const items = data?.flatMap((d) => d.items) ?? [];
|
|
|
|
|
const canLoadMore = (data?.at(-1)?.items.length ?? 0) === pageSize;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div>
|
|
|
|
|
<div style={{ display: "flex", gap: 8, marginBottom: 8 }}>
|
|
|
|
|
<span>정렬:</span>
|
2025-10-09 16:35:19 +09:00
|
|
|
<a
|
|
|
|
|
href={`${q ? "/search" : "/"}?${(() => { const p = new URLSearchParams(); if (q) p.set("q", q); if (boardId) p.set("boardId", boardId); p.set("sort", "recent"); return p.toString(); })()}`}
|
|
|
|
|
style={{ textDecoration: sort === "recent" ? "underline" : "none" }}
|
|
|
|
|
>
|
|
|
|
|
최신
|
|
|
|
|
</a>
|
|
|
|
|
<a
|
|
|
|
|
href={`${q ? "/search" : "/"}?${(() => { const p = new URLSearchParams(); if (q) p.set("q", q); if (boardId) p.set("boardId", boardId); p.set("sort", "popular"); return p.toString(); })()}`}
|
|
|
|
|
style={{ textDecoration: sort === "popular" ? "underline" : "none" }}
|
|
|
|
|
>
|
|
|
|
|
인기
|
|
|
|
|
</a>
|
2025-10-09 16:31:46 +09:00
|
|
|
</div>
|
|
|
|
|
<ul style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
|
|
|
|
{items.map((p) => (
|
|
|
|
|
<li key={p.id} style={{ padding: 12, border: "1px solid #eee", borderRadius: 8 }}>
|
|
|
|
|
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
2025-10-09 16:49:06 +09:00
|
|
|
<strong><a href={`/posts/${p.id}`}>{p.title}</a></strong>
|
2025-10-09 16:31:46 +09:00
|
|
|
<span style={{ opacity: 0.7 }}>{new Date(p.createdAt).toLocaleString()}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div style={{ fontSize: 12, opacity: 0.8 }}>
|
|
|
|
|
추천 {p.stat?.recommendCount ?? 0} · 조회 {p.stat?.views ?? 0} · 댓글 {p.stat?.commentsCount ?? 0}
|
|
|
|
|
</div>
|
|
|
|
|
</li>
|
|
|
|
|
))}
|
|
|
|
|
</ul>
|
|
|
|
|
<div style={{ marginTop: 12 }}>
|
|
|
|
|
<button disabled={!canLoadMore || isLoading} onClick={() => setSize(size + 1)}>
|
|
|
|
|
{isLoading ? "로딩 중..." : canLoadMore ? "더 보기" : "끝"}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|