Merge branch 'subwork' into mainwork

This commit is contained in:
koreacomp5
2025-11-02 04:59:21 +09:00
3 changed files with 43 additions and 17 deletions

View File

@@ -188,7 +188,6 @@ async function upsertBoards(admin, categoryMap) {
{ name: "이벤트", slug: "event", description: "이벤트", sortOrder: 4, requiredTags: { required: ["이벤트"] } },
{ name: "자유게시판", slug: "free", description: "자유", sortOrder: 5 },
{ name: "무엇이든", slug: "qna", description: "무엇이든 물어보세요", sortOrder: 6 },
{ name: "마사지꿀팁", slug: "tips", description: "팁", sortOrder: 7 },
{ name: "익명게시판", slug: "anonymous", description: "익명", sortOrder: 8, allowAnonymousPost: true, allowSecretComment: true },
{ name: "청와대", slug: "blue-house", description: "레벨 제한", sortOrder: 10, readLevel: "member" },
// 특수

View File

@@ -13,7 +13,7 @@ export function BoardToolbar({ boardId }: { boardId: string }) {
const onChangeSort = (e: React.ChangeEvent<HTMLSelectElement>) => {
const next = new URLSearchParams(sp.toString());
next.set("sort", e.target.value);
router.push(`/boards/${boardId}?${next.toString()}`);
router.push(`/boards/${boardId}?${next.toString()}` , { scroll: false });
};
const onChangePeriod = (e: React.ChangeEvent<HTMLSelectElement>) => {
@@ -26,7 +26,7 @@ export function BoardToolbar({ boardId }: { boardId: string }) {
if (v === "1w") now.setDate(now.getDate() - 7);
if (v === "1m") now.setMonth(now.getMonth() - 1);
if (v === "all") next.delete("start"); else next.set("start", now.toISOString());
router.push(`/boards/${boardId}?${next.toString()}`);
router.push(`/boards/${boardId}?${next.toString()}` , { scroll: false });
};
const onSubmit = (formData: FormData) => {
@@ -41,7 +41,7 @@ export function BoardToolbar({ boardId }: { boardId: string }) {
next.delete("author");
if (text) next.set("q", text); else next.delete("q");
}
router.push(`/boards/${boardId}?${next.toString()}`);
router.push(`/boards/${boardId}?${next.toString()}` , { scroll: false });
};
return (

View File

@@ -1,9 +1,9 @@
"use client";
import useSWRInfinite from "swr/infinite";
import useSWR from "swr";
import { useEffect, useMemo, useState } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import Link from "next/link";
import { useRouter, useSearchParams } from "next/navigation";
import { useSearchParams } from "next/navigation";
import ViewsIcon from "@/app/svgs/ViewsIcon";
import LikeIcon from "@/app/svgs/LikeIcon";
import CommentIcon from "@/app/svgs/CommentIcon";
@@ -31,8 +31,9 @@ const fetcher = (url: string) => fetch(url).then((r) => r.json());
export function PostList({ boardId, sort = "recent", q, tag, author, start, end, variant = "default", newPostHref }: { boardId?: string; sort?: "recent" | "popular"; q?: string; tag?: string; author?: string; start?: string; end?: string; variant?: "default" | "board"; newPostHref?: string }) {
const pageSize = 10;
const router = useRouter();
const sp = useSearchParams();
const listContainerRef = useRef<HTMLDivElement | null>(null);
const [lockedMinHeight, setLockedMinHeight] = useState<number | null>(null);
const getKey = (index: number, prev: Resp | null) => {
if (prev && prev.items.length === 0) return null;
@@ -76,6 +77,21 @@ export function PostList({ boardId, sort = "recent", q, tag, author, start, end,
const items = variant === "board" ? itemsSingle : itemsInfinite;
const isEmpty = variant === "board" ? isEmptySingle : isEmptyInfinite;
// 잠깐 높이 고정: 페이지 변경으로 재로딩될 때 현재 높이를 min-height로 유지
const lockHeight = () => {
const el = listContainerRef.current;
if (!el) return;
const h = el.offsetHeight;
if (h > 0) setLockedMinHeight(h);
};
// 로딩이 끝나면 해제
useEffect(() => {
if (variant === "board" && !isLoadingSingle) {
setLockedMinHeight(null);
}
}, [variant, isLoadingSingle]);
const initials = (name?: string | null) => (name ? name.trim().slice(0, 1) : "익");
return (
@@ -122,10 +138,11 @@ export function PostList({ boardId, sort = "recent", q, tag, author, start, end,
)}
{/* 아이템들 */}
<ul className="divide-y divide-[#ececec]">
{items.map((p) => (
<li key={p.id} className={`px-4 ${variant === "board" ? "py-2.5" : "py-3 md:py-3"} hover:bg-neutral-50 transition-colors`}>
<div className="grid grid-cols-1 md:grid-cols-[20px_1fr_120px_120px_80px] items-center gap-2">
<div ref={listContainerRef} style={{ minHeight: lockedMinHeight ? `${lockedMinHeight}px` : undefined }}>
<ul className="divide-y divide-[#ececec]">
{items.map((p) => (
<li key={p.id} className={`px-4 ${variant === "board" ? "py-2.5" : "py-3 md:py-3"} hover:bg-neutral-50 transition-colors`}>
<div className="grid grid-cols-1 md:grid-cols-[20px_1fr_120px_120px_80px] items-center gap-2">
{/* bullet/공지 아이콘 자리 */}
<div className="hidden md:flex items-center justify-center text-[#f94b37]">{p.isPinned ? "★" : "•"}</div>
@@ -153,9 +170,10 @@ export function PostList({ boardId, sort = "recent", q, tag, author, start, end,
</div>
<div className="md:w-[80px] text-xs text-neutral-500 text-right">{new Date(p.createdAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}</div>
</div>
</li>
))}
</ul>
</li>
))}
</ul>
</div>
{/* 페이지네이션 */}
{!isEmpty && (
@@ -165,11 +183,14 @@ export function PostList({ boardId, sort = "recent", q, tag, author, start, end,
{/* Previous */}
<button
onClick={() => {
lockHeight();
const next = Math.max(1, page - 1);
setPage(next);
const nextSp = new URLSearchParams(Array.from(sp.entries()));
nextSp.set("page", String(next));
router.push(`?${nextSp.toString()}`);
if (typeof window !== "undefined") {
window.history.replaceState(null, "", `?${nextSp.toString()}`);
}
}}
disabled={page <= 1}
className="h-9 px-3 rounded-md border border-neutral-300 bg-white text-sm disabled:opacity-50"
@@ -196,10 +217,13 @@ export function PostList({ boardId, sort = "recent", q, tag, author, start, end,
<button
key={`p-${n}-${idx}`}
onClick={() => {
lockHeight();
setPage(n);
const nextSp = new URLSearchParams(Array.from(sp.entries()));
nextSp.set("page", String(n));
router.push(`?${nextSp.toString()}`);
if (typeof window !== "undefined") {
window.history.replaceState(null, "", `?${nextSp.toString()}`);
}
}}
aria-current={n === page ? "page" : undefined}
className={`h-9 w-9 rounded-md border ${n === page ? "border-neutral-300 text-[#f94b37] font-semibold" : "border-neutral-300 text-neutral-900"}`}
@@ -215,11 +239,14 @@ export function PostList({ boardId, sort = "recent", q, tag, author, start, end,
{/* Next */}
<button
onClick={() => {
lockHeight();
const next = Math.min(totalPages, page + 1);
setPage(next);
const nextSp = new URLSearchParams(Array.from(sp.entries()));
nextSp.set("page", String(next));
router.push(`?${nextSp.toString()}`);
if (typeof window !== "undefined") {
window.history.replaceState(null, "", `?${nextSp.toString()}`);
}
}}
disabled={page >= totalPages}
className="h-9 px-3 rounded-md border border-neutral-300 bg-white text-sm disabled:opacity-50"