- {commentEnabled !== null && (
+ {showCommentToggle && (
<>
댓글 허용
>
diff --git a/src/components/admin/board/BoardPageContent.tsx b/src/components/admin/board/BoardPageContent.tsx
index a66129fe..90dc9be8 100644
--- a/src/components/admin/board/BoardPageContent.tsx
+++ b/src/components/admin/board/BoardPageContent.tsx
@@ -16,10 +16,9 @@ import {
} from '@dnd-kit/sortable';
import { useQueryClient } from '@tanstack/react-query';
-import { Icon, Skeleton } from '@/components/ui';
+import { Icon } from '@/components/ui';
import { InfoCircleIcon } from '@/assets/icons';
import { BoardCard } from '@/components/admin/board/BoardCard';
-import { BoardCardSkeleton } from '@/components/admin/board/BoardCardSkeleton';
import { BoardToolbar } from '@/components/admin/board/BoardToolbar';
import { CreateBoardModal } from '@/components/admin/board/modal/CreateBoardModal';
import { EditBoardModal } from '@/components/admin/board/modal/EditBoardModal';
@@ -28,22 +27,43 @@ import { useBoardDragReorder } from '@/hooks/admin';
import { useAdminBoardsQuery } from '@/hooks/queries/admin/useAdminBoardsQuery';
import { useCreateBoardMutation } from '@/hooks/queries/admin/useCreateBoardMutation';
import { useUpdateBoardMutation } from '@/hooks/queries/admin/useUpdateBoardMutation';
+import { useToggleBoardCommentMutation } from '@/hooks/queries/admin/useToggleBoardCommentMutation';
import { useDeleteBoardMutation } from '@/hooks/queries/admin/useDeleteBoardMutation';
import { useUpdateBoardOrderMutation } from '@/hooks/queries/admin/useUpdateBoardOrderMutation';
import { adminBoardQueryKeys } from '@/hooks/queries/admin/boardQueryKeys';
import { ADMIN_BOARD_ERROR, getApiErrorCode, getApiErrorMessage } from '@/lib/apis/adminBoard';
import { useClubId } from '@/stores';
-import { toastError } from '@/stores/useToastStore';
+import { toastError, toastSuccess } from '@/stores/useToastStore';
import { toApiPermission } from '@/utils/admin/boardMapper';
import { MAX_CUSTOM_BOARDS } from '@/constants/admin/board.constants';
-import type { Board, BoardListCache } from '@/types/admin/board';
+import type { Board, BoardKind, BoardListCache } from '@/types/admin/board';
import type { BoardFormData } from '@/components/admin/board/modal/constants';
import { SortableBoardCard } from './SortableBoardCard';
+import { BoardAdminSkeleton } from './BoardAdminSkeleton';
function subscribeMounted() {
return () => {};
}
+const FIXED_BOARD_ORDER: BoardKind[] = ['ALL', 'NOTICE'];
+
+function compareFixedBoards(a: Board, b: Board) {
+ const ai = FIXED_BOARD_ORDER.indexOf(a.kind);
+ const bi = FIXED_BOARD_ORDER.indexOf(b.kind);
+ return (ai === -1 ? FIXED_BOARD_ORDER.length : ai) - (bi === -1 ? FIXED_BOARD_ORDER.length : bi);
+}
+
+function handleNameMutationError(setNameError: (msg: string) => void) {
+ return (err: unknown) => {
+ const code = getApiErrorCode(err);
+ if (code === ADMIN_BOARD_ERROR.DUPLICATE_NAME) {
+ setNameError('같은 이름의 게시판이 이미 있어요.');
+ } else {
+ toastError(getApiErrorMessage(err));
+ }
+ };
+}
+
function BoardPageContent() {
const clubId = useClubId();
const queryClient = useQueryClient();
@@ -53,6 +73,21 @@ function BoardPageContent() {
const [createNameError, setCreateNameError] = useState
(null);
const [editingBoardId, setEditingBoardId] = useState(null);
const [editNameError, setEditNameError] = useState(null);
+ const [pendingToggleIds, setPendingToggleIds] = useState>(() => new Set());
+
+ const addPendingToggle = (boardId: number) =>
+ setPendingToggleIds((prev) => {
+ const next = new Set(prev);
+ next.add(boardId);
+ return next;
+ });
+
+ const removePendingToggle = (boardId: number) =>
+ setPendingToggleIds((prev) => {
+ const next = new Set(prev);
+ next.delete(boardId);
+ return next;
+ });
const mounted = useSyncExternalStore(
subscribeMounted,
() => true,
@@ -70,31 +105,26 @@ function BoardPageContent() {
const { handleDragStart, handleDragEnd } = useBoardDragReorder({ onReorder: updateBoardOrder });
const { mutate: createBoard } = useCreateBoardMutation({
- onSuccess: () => setCreateModalOpen(false),
- onError: (err) => {
- const code = getApiErrorCode(err);
- if (code === ADMIN_BOARD_ERROR.DUPLICATE_NAME) {
- setCreateNameError('같은 이름의 게시판이 이미 있어요.');
- } else {
- toastError(getApiErrorMessage(err));
- }
+ onSuccess: () => {
+ setCreateModalOpen(false);
+ toastSuccess('게시판이 생성되었어요.');
},
+ onError: handleNameMutationError(setCreateNameError),
});
const { mutate: updateBoard } = useUpdateBoardMutation({
- onSuccess: () => setEditingBoardId(null),
- onError: (err) => {
- const code = getApiErrorCode(err);
- if (code === ADMIN_BOARD_ERROR.DUPLICATE_NAME) {
- setEditNameError('같은 이름의 게시판이 이미 있어요.');
- } else {
- toastError(getApiErrorMessage(err));
- }
+ onSuccess: () => {
+ setEditingBoardId(null);
+ toastSuccess('게시판이 수정되었어요.');
},
+ onError: handleNameMutationError(setEditNameError),
});
const { mutate: deleteBoard } = useDeleteBoardMutation({
- onSuccess: () => setEditingBoardId(null),
+ onSuccess: () => {
+ setEditingBoardId(null);
+ toastSuccess('게시판이 삭제되었어요.');
+ },
onError: (err) => {
const code = getApiErrorCode(err);
if (code === ADMIN_BOARD_ERROR.BOARD_NOT_FOUND) {
@@ -105,7 +135,8 @@ function BoardPageContent() {
},
});
- const { mutate: toggleComment } = useUpdateBoardMutation({
+ const { mutate: toggleComment } = useToggleBoardCommentMutation({
+ onSuccess: () => toastSuccess('댓글 허용 설정을 변경했어요.'),
onError: (err) => toastError(getApiErrorMessage(err)),
});
@@ -114,36 +145,7 @@ function BoardPageContent() {
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
);
- if (isLoading || !data) {
- return (
-
- {/* Toolbar skeleton */}
-
-
-
-
-
- {/* Board card skeletons */}
-
-
- );
- }
+ if (isLoading || !data) return ;
const { boards } = data;
@@ -159,6 +161,8 @@ function BoardPageContent() {
};
const handleToggleComments = (boardId: number, next: boolean) => {
+ if (pendingToggleIds.has(boardId)) return;
+
const target = boards.find((b) => b.boardId === boardId);
if (!target) return;
@@ -169,11 +173,12 @@ function BoardPageContent() {
boards: prev.boards.map((b) => (b.boardId === boardId ? { ...b, commentEnabled: next } : b)),
}));
+ addPendingToggle(boardId);
+
toggleComment(
{
boardId,
body: {
- name: target.name,
description: target.description,
commentEnabled: next,
...toApiPermission(target.visibility),
@@ -183,6 +188,7 @@ function BoardPageContent() {
onError: () => {
queryClient.setQueryData(cacheKey, snapshot);
},
+ onSettled: () => removePendingToggle(boardId),
},
);
};
@@ -225,7 +231,7 @@ function BoardPageContent() {
? boards.filter((b) => b.name.toLowerCase().includes(query))
: boards;
- const fixedBoards = filteredBoards.filter((b) => !b.editable);
+ const fixedBoards = filteredBoards.filter((b) => !b.editable).sort(compareFixedBoards);
const customBoards = filteredBoards.filter((b) => b.editable);
const totalCustomCount = boards.filter((b) => b.editable).length;
const reachedLimit = totalCustomCount >= MAX_CUSTOM_BOARDS;
@@ -233,6 +239,16 @@ function BoardPageContent() {
const editingBoard =
editingBoardId !== null ? (boards.find((b) => b.boardId === editingBoardId) ?? null) : null;
+ const getToggleProps = (board: Board) => ({
+ onToggleComments: (next: boolean) => handleToggleComments(board.boardId, next),
+ commentTogglePending: pendingToggleIds.has(board.boardId),
+ });
+
+ const getEditableProps = (board: Board) => ({
+ onEdit: () => setEditingBoardId(board.boardId),
+ onDelete: () => handleMoveToTrash(board),
+ });
+
return (
setTrashModalOpen(true)}
onCreateClick={
reachedLimit
- ? () => toastError(`추가 게시판은 최대 ${MAX_CUSTOM_BOARDS}개까지 만들 수 있어요.`)
+ ? () => toastError(`게시판은 최대 ${MAX_CUSTOM_BOARDS}개까지 만들 수 있어요.`)
: () => setCreateModalOpen(true)
}
/>
@@ -254,11 +270,7 @@ function BoardPageContent() {
{fixedBoards.map((board, index) => (
{index > 0 && }
- handleToggleComments(board.boardId, next)}
- />
+
))}
@@ -276,9 +288,8 @@ function BoardPageContent() {
key={board.boardId}
board={board}
draggable={false}
- onToggleComments={(next) => handleToggleComments(board.boardId, next)}
- onEdit={() => setEditingBoardId(board.boardId)}
- onDelete={() => handleMoveToTrash(board)}
+ {...getToggleProps(board)}
+ {...getEditableProps(board)}
/>
))}
@@ -298,9 +309,8 @@ function BoardPageContent() {