This commit is contained in:
koreacomp5
2025-10-24 21:24:51 +09:00
parent 2668ade05f
commit 3d850188fd
14 changed files with 497 additions and 55 deletions

View File

@@ -9,6 +9,7 @@ type Item = {
status: string;
stat?: { recommendCount: number; views: number; commentsCount: number } | null;
postTags?: { tag: { name: string; slug: string } }[];
author?: { nickname: string } | null;
};
type Resp = {
@@ -40,44 +41,69 @@ export function PostList({ boardId, sort = "recent", q, tag, author, start, end
const canLoadMore = (data?.at(-1)?.items.length ?? 0) === pageSize;
return (
<div>
<div style={{ display: "flex", gap: 8, marginBottom: 8 }}>
<span>:</span>
<div className="w-full">
{/* 정렬 스위치 */}
<div className="mb-2 flex items-center gap-3 text-sm">
<span className="text-neutral-500"></span>
<a
className={sort === "recent" ? "underline" : "hover:underline"}
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
className={sort === "popular" ? "underline" : "hover:underline"}
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>
</div>
<ul style={{ display: "flex", flexDirection: "column", gap: 8 }}>
{/* 리스트 테이블 헤더 */}
<div className="hidden md:grid grid-cols-[1fr_auto_auto_auto] items-center px-4 py-2 text-xs text-neutral-500 border-b border-neutral-200">
<div></div>
<div className="w-28 text-center"></div>
<div className="w-24 text-center"></div>
<div className="w-24 text-right"></div>
</div>
{/* 아이템들 */}
<ul className="divide-y divide-neutral-100">
{items.map((p) => (
<li key={p.id} style={{ padding: 12, border: "1px solid #eee", borderRadius: 8 }}>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<strong><a href={`/posts/${p.id}`}>{p.title}</a></strong>
<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>
{!!p.postTags?.length && (
<div style={{ marginTop: 6, display: "flex", gap: 6, flexWrap: "wrap", fontSize: 12 }}>
{p.postTags?.map((pt) => (
<a key={pt.tag.slug} href={`/search?tag=${pt.tag.slug}`}>#{pt.tag.name}</a>
))}
<li key={p.id} className="px-4 py-3 hover:bg-neutral-50 transition-colors">
<div className="grid grid-cols-1 md:grid-cols-[1fr_auto_auto_auto] items-center gap-2">
<div className="min-w-0">
<a href={`/posts/${p.id}`} className="block truncate text-[15px] md:text-base text-neutral-900">
{p.isPinned && <span className="mr-2 inline-flex items-center rounded bg-orange-100 text-orange-700 px-1.5 py-0.5 text-[11px]"></span>}
{p.title}
</a>
{!!p.postTags?.length && (
<div className="mt-1 hidden md:flex flex-wrap gap-1 text-[11px] text-neutral-500">
{p.postTags?.map((pt) => (
<a key={pt.tag.slug} href={`/search?tag=${pt.tag.slug}`} className="hover:underline">#{pt.tag.name}</a>
))}
</div>
)}
</div>
)}
<div className="md:w-28 text-xs text-neutral-600 text-center">{p.author?.nickname ?? "익명"}</div>
<div className="md:w-24 text-[11px] text-neutral-600 text-center flex md:block gap-3 md:gap-0">
<span>👍 {p.stat?.recommendCount ?? 0}</span>
<span>👁 {p.stat?.views ?? 0}</span>
<span>💬 {p.stat?.commentsCount ?? 0}</span>
</div>
<div className="md:w-24 text-xs text-neutral-500 text-right">{new Date(p.createdAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}</div>
</div>
</li>
))}
</ul>
<div style={{ marginTop: 12 }}>
<button disabled={!canLoadMore || isLoading} onClick={() => setSize(size + 1)}>
{/* 페이지 더보기 */}
<div className="mt-3 flex justify-center">
<button
className="h-9 px-4 rounded-md border border-neutral-300 bg-white text-sm hover:bg-neutral-100 disabled:opacity-50"
disabled={!canLoadMore || isLoading}
onClick={() => setSize(size + 1)}
>
{isLoading ? "로딩 중..." : canLoadMore ? "더 보기" : "끝"}
</button>
</div>