7.3 태그/카테고리 모델 및 UI o
This commit is contained in:
@@ -100,6 +100,7 @@ export async function GET(req: Request) {
|
|||||||
isPinned: true,
|
isPinned: true,
|
||||||
status: true,
|
status: true,
|
||||||
stat: { select: { recommendCount: true, views: true, commentsCount: true } },
|
stat: { select: { recommendCount: true, views: true, commentsCount: true } },
|
||||||
|
postTags: { select: { tag: { select: { name: true, slug: true } } } },
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|||||||
13
src/app/api/tags/route.ts
Normal file
13
src/app/api/tags/route.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
const tags = await prisma.tag.findMany({
|
||||||
|
orderBy: { name: "asc" },
|
||||||
|
take: 100,
|
||||||
|
select: { slug: true, name: true },
|
||||||
|
});
|
||||||
|
return NextResponse.json({ tags });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -8,6 +8,7 @@ type Item = {
|
|||||||
isPinned: boolean;
|
isPinned: boolean;
|
||||||
status: string;
|
status: string;
|
||||||
stat?: { recommendCount: number; views: number; commentsCount: number } | null;
|
stat?: { recommendCount: number; views: number; commentsCount: number } | null;
|
||||||
|
postTags?: { tag: { name: string; slug: string } }[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type Resp = {
|
type Resp = {
|
||||||
@@ -65,6 +66,13 @@ export function PostList({ boardId, sort = "recent", q, tag, author, start, end
|
|||||||
<div style={{ fontSize: 12, opacity: 0.8 }}>
|
<div style={{ fontSize: 12, opacity: 0.8 }}>
|
||||||
추천 {p.stat?.recommendCount ?? 0} · 조회 {p.stat?.views ?? 0} · 댓글 {p.stat?.commentsCount ?? 0}
|
추천 {p.stat?.recommendCount ?? 0} · 조회 {p.stat?.views ?? 0} · 댓글 {p.stat?.commentsCount ?? 0}
|
||||||
</div>
|
</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>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
21
src/app/components/TagFilter.tsx
Normal file
21
src/app/components/TagFilter.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
"use client";
|
||||||
|
import useSWR from "swr";
|
||||||
|
|
||||||
|
const fetcher = (url: string) => fetch(url).then((r) => r.json());
|
||||||
|
|
||||||
|
export function TagFilter({ basePath, current }: { basePath: string; current?: string }) {
|
||||||
|
const { data } = useSWR<{ tags: { slug: string; name: string }[] }>("/api/tags", fetcher);
|
||||||
|
const tags = data?.tags ?? [];
|
||||||
|
return (
|
||||||
|
<div style={{ display: "flex", gap: 8, flexWrap: "wrap", marginBottom: 8 }}>
|
||||||
|
<a href={`${basePath}`} style={{ textDecoration: !current ? "underline" : "none" }}>전체</a>
|
||||||
|
{tags.map((t) => (
|
||||||
|
<a key={t.slug} href={`${basePath}?tag=${t.slug}`} style={{ textDecoration: current === t.slug ? "underline" : "none" }}>
|
||||||
|
#{t.name}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
[게시판/컨텐츠]
|
[게시판/컨텐츠]
|
||||||
7.1 게시글 CRUD API 및 페이지 연동 o
|
7.1 게시글 CRUD API 및 페이지 연동 o
|
||||||
7.2 목록 페이징/정렬/검색(제목/태그/작성자/기간) o
|
7.2 목록 페이징/정렬/검색(제목/태그/작성자/기간) o
|
||||||
7.3 태그/카테고리 모델 및 UI
|
7.3 태그/카테고리 모델 및 UI o
|
||||||
7.4 첨부 업로드 및 본문 삽입
|
7.4 첨부 업로드 및 본문 삽입
|
||||||
7.5 추천/신고, 조회수 카운트
|
7.5 추천/신고, 조회수 카운트
|
||||||
7.6 익명/비밀댓글/비댓 해시 처리
|
7.6 익명/비밀댓글/비댓 해시 처리
|
||||||
|
|||||||
Reference in New Issue
Block a user