From 58af463585886ef84d0cdb51b98006b9b3aedd2b Mon Sep 17 00:00:00 2001 From: koreacomp5 Date: Sun, 2 Nov 2025 07:01:42 +0900 Subject: [PATCH] =?UTF-8?q?=E3=85=87=E3=85=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cursor/.prompt/1101.md | 15 ++-- prisma/seed.js | 28 ++++++- src/app/page.tsx | 164 ++++++++++++++++++++++++++++++++++------ 3 files changed, 174 insertions(+), 33 deletions(-) diff --git a/.cursor/.prompt/1101.md b/.cursor/.prompt/1101.md index f37a43d..f676852 100644 --- a/.cursor/.prompt/1101.md +++ b/.cursor/.prompt/1101.md @@ -1,17 +1,18 @@ -배너 디테일 -카드 디테일 -메인 디테일 프리뷰, 글, 스페셜_랭크 -기본 리스트 , 글이 없습니다. -글쓰기 -글뷰, 댓글 +리스트 +메인 게시판 일반 +메인 게시판 프리뷰 +메인 게시판 스페셜랭크 + +기본 리스트 스페셜_랭크 스페셜_출석 스페셜_제휴업체 스페셜_제휴업체지도 +게시글 뷰 + 댓글 로그인관련 +회원가입 페이지 회원쪽지 -링크로들어오면 보이고 거기서 페이지이동하면안보이게 \ No newline at end of file +링크로들어오면 보이고 거기서 페이지 이동하면 안보이게 \ No newline at end of file diff --git a/prisma/seed.js b/prisma/seed.js index 51e1593..2143f36 100644 --- a/prisma/seed.js +++ b/prisma/seed.js @@ -199,9 +199,10 @@ async function upsertBoards(admin, categoryMap) { ]; const created = []; - // 특수 랭킹 뷰 타입 ID 조회 (사전에 upsertViewTypes로 생성됨) + // 특수 랭킹/텍스트 뷰 타입 ID 조회 (사전에 upsertViewTypes로 생성됨) const mainSpecial = await prisma.boardViewType.findUnique({ where: { key: "main_special_rank" } }); const listSpecial = await prisma.boardViewType.findUnique({ where: { key: "list_special_rank" } }); + const mainText = await prisma.boardViewType.findUnique({ where: { key: "main_text" } }); for (const b of boards) { // 카테고리 매핑 규칙 (트리 기준 상위 카테고리) @@ -233,7 +234,10 @@ async function upsertBoards(admin, categoryMap) { allowAnonymousPost: !!b.allowAnonymousPost, readLevel: b.readLevel || undefined, categoryId: category ? category.id : undefined, - ...(b.slug === "ranking" && mainSpecial ? { mainPageViewTypeId: mainSpecial.id } : {}), + // 메인뷰: 기본이 아니라 텍스트(main_text)로 설정, 랭킹 보드는 특수 랭킹 유지 + ...(b.slug === "ranking" + ? (mainSpecial ? { mainPageViewTypeId: mainSpecial.id } : {}) + : (mainText ? { mainPageViewTypeId: mainText.id } : {})), ...(b.slug === "ranking" && listSpecial ? { listViewTypeId: listSpecial.id } : {}), }, create: { @@ -244,7 +248,10 @@ async function upsertBoards(admin, categoryMap) { allowAnonymousPost: !!b.allowAnonymousPost, readLevel: b.readLevel || undefined, categoryId: category ? category.id : undefined, - ...(b.slug === "ranking" && mainSpecial ? { mainPageViewTypeId: mainSpecial.id } : {}), + // 메인뷰: 기본이 아니라 텍스트(main_text)로 설정, 랭킹 보드는 특수 랭킹 유지 + ...(b.slug === "ranking" + ? (mainSpecial ? { mainPageViewTypeId: mainSpecial.id } : {}) + : (mainText ? { mainPageViewTypeId: mainText.id } : {})), ...(b.slug === "ranking" && listSpecial ? { listViewTypeId: listSpecial.id } : {}), }, }); @@ -382,6 +389,20 @@ async function seedBanners() { await prisma.banner.createMany({ data: items }); } +async function seedMainpageVisibleBoards(boards) { + const SETTINGS_KEY = "mainpage_settings"; + const setting = await prisma.setting.findUnique({ where: { key: SETTINGS_KEY } }); + const current = setting ? JSON.parse(setting.value) : {}; + const wantSlugs = new Set(["notice", "free", "ranking"]); + const visibleBoardIds = boards.filter((b) => wantSlugs.has(b.slug)).map((b) => b.id); + const next = { ...current, visibleBoardIds }; + await prisma.setting.upsert({ + where: { key: SETTINGS_KEY }, + update: { value: JSON.stringify(next) }, + create: { key: SETTINGS_KEY, value: JSON.stringify(next) }, + }); +} + async function main() { await upsertRoles(); const admin = await upsertAdmin(); @@ -390,6 +411,7 @@ async function main() { await createRandomUsers(100); await removeNonPrimaryBoards(); const boards = await upsertBoards(admin, categoryMap); + await seedMainpageVisibleBoards(boards); await createPostsForAllBoards(boards, 100, admin); await seedPartnerShops(); await seedBanners(); diff --git a/src/app/page.tsx b/src/app/page.tsx index a7b54e2..45b7b4a 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -18,28 +18,146 @@ export default async function Home({ searchParams }: { searchParams: Promise<{ s const showPartnerShops: boolean = parsed.showPartnerShops ?? true; const visibleBoardIds: string[] = Array.isArray(parsed.visibleBoardIds) ? parsed.visibleBoardIds : []; - // 보드 메타데이터 (이름 표시용) + // 보드 메타데이터 (메인뷰 타입 포함) const boardsMeta = visibleBoardIds.length - ? await prisma.board.findMany({ where: { id: { in: visibleBoardIds } }, select: { id: true, name: true } }) + ? await prisma.board.findMany({ + where: { id: { in: visibleBoardIds } }, + select: { + id: true, + name: true, + slug: true, + category: { select: { id: true, name: true } }, + mainPageViewType: { select: { key: true } }, + }, + }) : []; - const idToMeta = new Map(boardsMeta.map((b) => [b.id, b] as const)); + const idToMeta = new Map( + boardsMeta.map((b) => [ + b.id, + { + id: b.id, + name: b.name, + slug: b.slug, + categoryId: b.category?.id ?? null, + categoryName: b.category?.name ?? "", + mainTypeKey: b.mainPageViewType?.key, + }, + ] as const) + ); const orderedBoards = visibleBoardIds .map((id) => idToMeta.get(id)) - .filter((v): v is { id: string; name: string } => Boolean(v)); + .filter((v): v is any => Boolean(v)); const firstTwo = orderedBoards.slice(0, 2); const restBoards = orderedBoards.slice(2); - const renderBoardPanel = (board: { id: string; name: string }) => ( -
-
- {board.name} - 더보기 + function formatDateYmd(d: Date) { + const yyyy = d.getFullYear(); + const mm = String(d.getMonth() + 1).padStart(2, "0"); + const dd = String(d.getDate()).padStart(2, "0"); + return `${yyyy}.${mm}.${dd}`; + } + + const renderBoardPanel = async (board: { id: string; name: string; slug: string; categoryId: string | null; categoryName: string; mainTypeKey?: string }) => { + // 같은 카테고리의 소분류(게시판) 탭 목록 + const siblingBoards = board.categoryId + ? await prisma.board.findMany({ + where: { categoryId: board.categoryId }, + select: { id: true, name: true, slug: true }, + orderBy: [{ sortOrder: "asc" }, { name: "asc" }], + }) + : []; + const isTextMain = board.mainTypeKey === "main_text"; + if (!isTextMain) { + return ( +
+
+
+
{board.categoryName || board.name}
+
+
+ {siblingBoards.map((sb) => ( + + {sb.name} + + ))} +
+
+
+
+ {board.name} + 더보기 +
+
+ +
+
+
+ ); + } + + const posts = await prisma.post.findMany({ + where: { boardId: board.id, status: "published" }, + select: { id: true, title: true, createdAt: true, stat: { select: { recommendCount: true } } }, + orderBy: { createdAt: "desc" }, + take: 8, + }); + + return ( +
+
+
+
{board.categoryName || board.name}
+
+
+ {siblingBoards.map((sb) => ( + + {sb.name} + + ))} +
+
+
+
+ {board.name} + 더보기 +
+
+
+
    + {posts.map((p) => ( +
  • +
    +
    +
    +
    +
    n
    +
    + {p.title} + +{p.stat?.recommendCount ?? 0} +
    + {formatDateYmd(new Date(p.createdAt))} +
    +
  • + ))} +
+
+
+
-
- -
-
- ); + ); + }; return (
@@ -77,7 +195,7 @@ export default async function Home({ searchParams }: { searchParams: Promise<{ s {/* 1행: 프로필 + 선택된 보드 2개 (최대 2개) */} {(firstTwo.length > 0) && ( -
+
@@ -144,9 +262,9 @@ export default async function Home({ searchParams }: { searchParams: Promise<{ s
- {firstTwo.map((b) => ( -
- {renderBoardPanel(b)} + {(await Promise.all(firstTwo.map((b) => renderBoardPanel(b)))).map((panel, idx) => ( +
+ {panel}
))}
@@ -156,14 +274,14 @@ export default async function Home({ searchParams }: { searchParams: Promise<{ s {/* 나머지 보드: 2개씩 다음 열로 렌더링 */} {restBoards.length > 0 && ( <> - {Array.from({ length: Math.ceil(restBoards.length / 2) }).map((_, i) => { + {Array.from({ length: Math.ceil(restBoards.length / 2) }).map(async (_, i) => { const pair = restBoards.slice(i * 2, i * 2 + 2); return ( -
+
- {pair.map((b) => ( -
- {renderBoardPanel(b)} + {(await Promise.all(pair.map((b) => renderBoardPanel(b)))).map((panel, idx) => ( +
+ {panel}
))}