Skip to content

[FE] 20260308 #267 기능추가관리자 대시보드 페이지 구현#289

Merged
discipline24 merged 8 commits into
mainfrom
20260308_#267-기능추가관리자-대시보드-페이지-구현
Mar 8, 2026

Hidden character warning

The head ref may contain hidden characters: "20260308_#267-\uae30\ub2a5\ucd94\uac00\uad00\ub9ac\uc790-\ub300\uc2dc\ubcf4\ub4dc-\ud398\uc774\uc9c0-\uad6c\ud604"
Merged

[FE] 20260308 #267 기능추가관리자 대시보드 페이지 구현#289
discipline24 merged 8 commits into
mainfrom
20260308_#267-기능추가관리자-대시보드-페이지-구현

Conversation

@sangkyu39
Copy link
Copy Markdown
Contributor

@sangkyu39 sangkyu39 commented Mar 8, 2026

관리자 대시보드 api 연결 및 게시판 버그 수정

Summary by CodeRabbit

  • 새로운 기능

    • 관리자 통계 대시보드 추가(방문자/사용자/게시판 차트, 기간 필터)
    • 실시간 활동 로그 스트리밍
  • 개선 사항

    • 관리 메뉴 항목 전면 활성화(게시물/출석/포인트/게임·툴 등)
    • 회원 관리 UI: 역할·상태를 시각적 뱃지로 표시
    • 게시판 성능 개선: 게시물별 캐시·검색 적용으로 로드 최적화
    • 게시판 생성/저장 시 UI 비활성화 및 "생성 중..." 피드백
    • 관리자 권한 확장: SYSTEM_ADMIN 접근 허용

Copilot AI review requested due to automatic review settings March 8, 2026 11:46
@sangkyu39 sangkyu39 linked an issue Mar 8, 2026 that may be closed by this pull request
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 8, 2026

Walkthrough

관리자 대시보드 페이지와 관련 컴포넌트/스타일이 추가되었고, 보드 페이지에 클라이언트 캐시와 경로 정규화 유틸이 도입되었습니다. 여러 관리자 UI(사이드바, 멤버 테이블, 활동 목록)와 API 유틸이 추가·수정되었습니다.

Changes

Cohort / File(s) Summary
관리자 대시보드 관련
frontend/src/pages/AdminDashboard.jsx, frontend/src/pages/AdminDashboard.module.css, frontend/src/components/AdminDashboard/AdminDashboard.jsx, frontend/src/components/AdminDashboard/AdminDashboard.module.css, frontend/src/utils/adminDashboardApi.js
새 AdminDashboard 페이지/컴포넌트 및 스타일 추가. 여러 통계 API 헬퍼 추가, 기간 필터·차트·실시간 활동 스트리밍 구현.
관리자 홈 UI & 사이드바
frontend/src/components/AdminHome/AdminSidebar.jsx, frontend/src/components/AdminHome/MemberList.jsx, frontend/src/components/AdminHome/MembersPanel.jsx, frontend/src/components/AdminHome/RecentActivitiesPanel.jsx, frontend/src/pages/AdminHome.module.css
관리자 메뉴 항목 활성화, 멤버 목록에 역할/상태 라벨·배지 스타일 추가, 최근 활동 빈 상태 처리 및 관련 CSS 추가. 사이드바에 홈페이지 링크 추가.
앱 라우팅·권한 변경
frontend/src/App.jsx, frontend/src/components/AdminRoute.jsx
/admin/dashboard 경로 등록. AdminRoute 권한 체크가 PRESIDENT 외에 SYSTEM_ADMIN도 허용하도록 확장.
보드 페이지 및 CreateSubBoardModal 변경
frontend/src/pages/Board.jsx, frontend/src/components/Board/CreateSubBoardModal.jsx
Board.jsx에 클라이언트 측 포스트 캐시(postCacheByBoardId), 검색 필터, 낙관적 업데이트, 서브보드 생성 플로우 및 동시성 guards 추가. CreateSubBoardModal에 isSaving prop 추가 및 제출/버튼 비활성화 로직 적용.
보드 라우트 유틸 및 사이드바 네비게이션 정규화
frontend/src/utils/boardRoute.js, frontend/src/components/Sidebar.jsx
정적 맵을 대체하는 이름/경로 정규화 유틸(normalizeBoardRouteSegment, normalizeBoardPath, toBoardPath) 도입. Sidebar가 정규화된 경로로 비교/네비게이션 수행하도록 변경.
기존 API/유틸 수정
frontend/src/utils/adminHomeData.js, frontend/src/utils/boardApi.js
adminHomeData가 Promise.allSettled로 부분 실패를 견딜 수 있게 변경 및 데이터 정규화 추가. boardApi.createSubBoard가 백엔드에서 boardId 미제공 시 안전한 반환 형태를 제공하도록 수정.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant BoardPage as Board Page
    participant Cache as Post Cache
    participant API as API Server
    participant UI as UI Components

    User->>BoardPage: 보드 선택 / 검색
    BoardPage->>Cache: postCacheByBoardId 확인
    alt 캐시 존재
        Cache-->>BoardPage: 캐시된 포스트 반환
        BoardPage->>UI: 필터링된 포스트 렌더링
    else 캐시 없음
        BoardPage->>API: fetchPostsByBoardIds 요청
        API-->>BoardPage: 보드별 포스트 반환
        BoardPage->>Cache: 캐시 업데이트
        BoardPage->>UI: 포스트 렌더링
    end

    User->>UI: 좋아요/북마크 클릭
    UI->>Cache: 캐시 낙관적 업데이트
    UI->>API: 상태 변경 요청
    alt API 성공
        API-->>BoardPage: 확인
    else API 실패
        API-->>BoardPage: 오류
        BoardPage->>Cache: 이전 스냅샷으로 롤백
    end
Loading
sequenceDiagram
    participant AdminUser as Admin
    participant AdminDashboard as Admin Dashboard
    participant API as API Server
    participant Stream as EventSource
    participant Charts as Charts

    AdminUser->>AdminDashboard: 기간 선택(7/30/90)
    AdminDashboard->>API: getVisitorsTrend / getBoardsDistribution / getUsersDistribution / getDashboardActivities
    API-->>AdminDashboard: 통계 데이터 응답
    AdminDashboard->>Charts: 차트 렌더링 데이터 전달

    AdminDashboard->>Stream: EventSource 연결 (activities stream)
    Stream-->>AdminDashboard: 실시간 활동 로그 전송
    AdminDashboard->>AdminDashboard: 로그 병합 및 중복 제거
    AdminDashboard->>Charts: 로그/뷰 갱신
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

FE

Suggested reviewers

  • DongEun02
  • gxuoo

Poem

🐰 대시보드에 별이 반짝이고,
캐시는 발빠르게 춤추네.
경로는 말끔히 정리되고,
사이드바는 활짝 열렸네.
새 기능들 보며 당근 한 입! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 관리자 대시보드 페이지 구현이라는 주요 변경사항을 명확하게 요약하고 있으며, 전체 PR의 핵심 목표와 부합합니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 20260308_#267-기능추가관리자-대시보드-페이지-구현

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

관리자 통계 대시보드 페이지를 새로 추가하고(실시간 활동 로그 포함) 관련 API 유틸을 연결하는 한편, 게시판 라우팅/하위게시판 생성 흐름 및 Admin Home 화면 데이터를 보강하는 PR입니다.

Changes:

  • 관리자 대시보드 페이지(UI/CSS) 및 대시보드 API 유틸 추가, AdminHome 데이터도 대시보드 엔드포인트 기반으로 일부 연결
  • 게시판 라우팅 유틸을 정규화/슬러그 기반으로 개편하고, Board/Sidebar에서 경로 정규화 적용
  • Board 페이지의 게시글 로딩/검색/캐시 및 하위 게시판 생성 UX 개선(중복 생성 방지 등)

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
frontend/src/utils/boardRoute.js 게시판 이름/경로 정규화 및 라우트 세그먼트 생성 로직 개편
frontend/src/utils/boardApi.js createSubBoard가 boardId 없이 성공하는 응답을 허용하도록 변경
frontend/src/utils/adminHomeData.js AdminHome 데이터 로딩을 대시보드 활동/방문자 API와 병렬로 조회하도록 개선
frontend/src/utils/adminDashboardApi.js 관리자 대시보드 통계/활동/스트림 URL API 래퍼 신규 추가
frontend/src/pages/Board.jsx 게시글 캐시/클라이언트 검색/탭 변경 로직 및 하위 게시판 생성 UX 개선
frontend/src/pages/AdminHome.module.css 최근 활동/회원 테이블/배지 스타일 등 UI 개선용 CSS 추가
frontend/src/pages/AdminDashboard.module.css AdminDashboard 레이아웃 CSS 신규 추가
frontend/src/pages/AdminDashboard.jsx 관리자 대시보드 페이지 라우트 엔트리 신규 추가
frontend/src/components/Sidebar.jsx 게시판 경로 비교/이동 시 normalizeBoardPath 적용
frontend/src/components/Board/CreateSubBoardModal.jsx 저장 중 UI/동작 제한(중복 submit/닫기 방지) 추가
frontend/src/components/AdminRoute.jsx 관리자 권한에 SYSTEM_ADMIN 포함
frontend/src/components/AdminHome/RecentActivitiesPanel.jsx 최근 활동 리스트 스크롤/빈 상태 UI 추가
frontend/src/components/AdminHome/MembersPanel.jsx MemberList에 styles 전달로 테이블/배지 스타일 적용
frontend/src/components/AdminHome/MemberList.jsx 회원 목록을 테이블+배지 형태로 개선(역할/상태 라벨링)
frontend/src/components/AdminHome/AdminSidebar.jsx 관리자 사이드바 메뉴/링크 구성 변경 및 홈 링크 추가
frontend/src/components/AdminDashbord/AdminDashbord.module.css 대시보드 카드/차트/로그 UI CSS 신규 추가
frontend/src/components/AdminDashbord/AdminDashbord.jsx 통계 차트/실시간 활동 로그(스트림) 대시보드 컴포넌트 신규 추가
frontend/src/App.jsx /admin/dashboard 라우트 등록
Comments suppressed due to low confidence (1)

frontend/src/components/AdminHome/AdminSidebar.jsx:131

  • The sidebar footer links to /admin/settings, but there is no matching route in App.jsx. Clicking this will lead to a missing page. Either implement/register the settings route or remove/disable the link until it exists.
        <Link
          to="/admin/settings"
          className={styles.settingsLink}
        >
          <Settings size={16} />
          설정
        </Link>

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread frontend/src/components/AdminHome/AdminSidebar.jsx
Comment thread frontend/src/utils/boardRoute.js
Comment thread frontend/src/pages/Board.jsx
Comment thread frontend/src/pages/Board.jsx
Comment thread frontend/src/components/AdminHome/MemberList.jsx
Comment thread frontend/src/pages/AdminDashboard.jsx
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/src/pages/Board.jsx (1)

356-398: ⚠️ Potential issue | 🟠 Major

생성 성공 후 목록 재조회만 실패해도 전체를 생성 실패로 안내합니다.

createSubBoard가 성공한 뒤 getSubBoards만 실패해도 catch로 내려가서 사용자는 실제 생성 여부를 알 수 없습니다. 이 경우 재시도로 동일한 하위 게시판이 중복 생성될 수 있으니, 생성 자체와 후속 새로고침 오류는 분리해서 처리하는 편이 안전합니다.

🔧 수정 예시
       const created = await boardApi.createSubBoard(
         subBoardName.trim(),
         currentBoardId
       );
       handleCloseSubBoardModal();

-      const subBoards = await boardApi.getSubBoards(currentBoardId);
+      let subBoards;
+      try {
+        subBoards = await boardApi.getSubBoards(currentBoardId);
+      } catch (refreshError) {
+        if (created?.boardId) {
+          setSubBoardTabs((prev) => [
+            ...prev,
+            { id: created.boardId, name: subBoardName.trim() },
+          ]);
+          setPostCacheByBoardId((prev) => ({
+            ...prev,
+            [created.boardId]: prev[created.boardId] || [],
+          }));
+        }
+        alert('하위 게시판은 생성되었지만 목록 새로고침에 실패했습니다.');
+        return;
+      }
+
       const tabs = [
         { id: ALL_TAB_ID, name: '전체 게시판' },
         ...(Array.isArray(subBoards)
           ? subBoards.map((board) => ({ id: board.boardId, name: board.boardName }))
           : []),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/pages/Board.jsx` around lines 356 - 398, The current flow treats
any error during getSubBoards as a failure of createSubBoard; change it so
createSubBoard is awaited inside its own try/catch and on success you
immediately close the modal, notify the user, and proceed to refresh sub-boards
in a separate try/catch; if getSubBoards fails, log or show a non-blocking
warning and fall back to using the created response (created?.boardId and
created?.boardName) to build tabs, setSubBoardTabs, setActiveSubBoard, update
postCacheByBoardId and setPosts (use created as a source when subBoards is
unavailable) so the created board is reflected even if the refresh failed;
specifically split the logic around createSubBoard and getSubBoards calls and
preserve use of symbols created, getSubBoards, setSubBoardTabs,
setActiveSubBoard, setPostCacheByBoardId, buildPostsFromCache, and setPosts.
🧹 Nitpick comments (5)
frontend/src/components/AdminHome/AdminSidebar.jsx (2)

99-106: NavLink의 내장 className 함수 활용 권장

현재 isActive 상태를 수동으로 계산하고 있지만, react-router-domNavLink는 이미 내장된 className 함수를 제공합니다. 이를 활용하면 중복 로직을 제거하고 중첩 라우트에서도 더 유연하게 처리할 수 있습니다.

♻️ NavLink className 함수 활용 제안
-              {section.items.map((item) => {
-                const isActive = pathname === item.href;
-                return (
-                  <li key={item.href}>
-                    <NavLink
-                      to={item.href}
-                      className={`${styles.menuLink} ${isActive ? styles.menuLinkActive : ''}`}
-                    >
-                      <item.icon size={16} />
-                      {item.label}
-                    </NavLink>
-                  </li>
-                );
-              })}
+              {section.items.map((item) => (
+                <li key={item.href}>
+                  <NavLink
+                    to={item.href}
+                    className={({ isActive }) =>
+                      `${styles.menuLink} ${isActive ? styles.menuLinkActive : ''}`
+                    }
+                    end
+                  >
+                    <item.icon size={16} />
+                    {item.label}
+                  </NavLink>
+                </li>
+              ))}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/AdminHome/AdminSidebar.jsx` around lines 99 - 106,
Replace the manual isActive computation and string interpolation with NavLink's
built-in className callback: remove the isActive variable and change the NavLink
to pass a function to className that returns `${styles.menuLink} ${isActive ?
styles.menuLinkActive : ''}` (using the callback's isActive argument) so active
state is handled by NavLink for routes like nested matches; update the NavLink
instance that currently references item.href and item.icon accordingly.

59-59: Optional chaining 사용은 불필요하지만 무해함

useLocation()은 항상 pathname 속성을 포함한 객체를 반환하므로 optional chaining(?.)은 기술적으로 불필요합니다. 다만 방어적 코딩으로서 해가 되지 않으므로 유지해도 무방합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/AdminHome/AdminSidebar.jsx` at line 59, The optional
chaining on pathname (const isDevSection = pathname?.startsWith('/admin/dev'))
is unnecessary because useLocation() always returns an object with pathname;
update the expression to use plain pathname.startsWith('/admin/dev') to remove
the redundant '?.' while keeping behavior identical (refer to isDevSection,
pathname, and useLocation to locate the code).
frontend/src/components/AdminRoute.jsx (1)

5-8: 역할 정의 중복 고려

변경 사항은 올바르며, Sidebar.jsx:12ADMIN_VISIBLE_ROLES 배열과 일치합니다. 두 파일 모두 관리자 역할을 별도로 정의하고 있어, 향후 역할이 변경될 경우 유지보수가 어려울 수 있습니다. 공유 상수 파일(예: constants/roles.js)로 통합하는 것을 권장합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/AdminRoute.jsx` around lines 5 - 8, Extract the admin
role list into a shared constant and import it where needed: create a constants
export (e.g., constants/roles.js) that defines ADMIN_VISIBLE_ROLES or
ADMIN_ROLES array containing 'PRESIDENT' and 'SYSTEM_ADMIN', then update the
isAdminRole function in AdminRoute.jsx to reference that shared constant instead
of hardcoding, and update Sidebar.jsx to import the same constant (refer to
isAdminRole and ADMIN_VISIBLE_ROLES to locate usage). Ensure the constant is
exported as a plain array and update imports accordingly.
frontend/src/components/AdminHome/MemberList.jsx (1)

17-22: 시스템 관리자 역할 스타일 누락

getRoleClassName에서 '시스템 관리자' 케이스가 명시적으로 처리되지 않아 roleNormal로 폴백됩니다. 시스템 관리자에게 별도 스타일이 필요하다면 추가를 고려하세요.

 const getRoleClassName = (roleLabel, styles) => {
+	if (roleLabel === '시스템 관리자') return styles.roleAdmin;
 	if (roleLabel === '회장') return styles.rolePresident;
 	if (roleLabel === '부회장') return styles.roleManager;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/AdminHome/MemberList.jsx` around lines 17 - 22,
getRoleClassName currently falls back to styles.roleNormal for unhandled labels;
add an explicit branch for the '시스템 관리자' label so it returns a dedicated style
(e.g., styles.roleSysAdmin) instead of roleNormal. Update the getRoleClassName
function to check if roleLabel === '시스템 관리자' and return the new styles property,
and ensure the corresponding CSS/classname (styles.roleSysAdmin) is defined
where styles is declared.
frontend/src/components/Sidebar.jsx (1)

101-103: 중복 변수 할당

targetPathnextPath가 동일한 값으로 할당됩니다. 가독성을 위해 하나로 통합하거나, 의도가 다르다면 명확히 구분하는 것이 좋습니다.

♻️ 제안된 수정
     const currentPath = normalizeBoardPath(location.pathname);
-    const targetPath = normalizeBoardPath(board.path);
-    const nextPath = normalizeBoardPath(board.path);
+    const targetPath = normalizeBoardPath(board.path);

-    if (currentPath === targetPath) {
-      nav(nextPath, {
+    if (currentPath === targetPath) {
+      nav(targetPath, {
         replace: false,
         state: { boardSwitchAt: Date.now() },
       });
     } else {
-      nav(nextPath);
+      nav(targetPath);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/Sidebar.jsx` around lines 101 - 103, currentPath,
targetPath and nextPath are all assigned via normalizeBoardPath(board.path)
causing targetPath and nextPath to be duplicates; either remove one and use a
single variable (e.g., targetPath) or if they are meant to represent different
semantics, compute them differently and give them clear names. Update the
Sidebar.jsx code around the normalizeBoardPath calls: keep currentPath =
normalizeBoardPath(location.pathname) and replace the two identical assignments
with a single descriptive variable (or adjust the logic that computes nextPath
if it should differ), then update any downstream references to use the
consolidated name (currentPath, targetPath or nextPath) so there are no
redundant variables.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/AdminDashbord/AdminDashbord.jsx`:
- Around line 148-153: loadActivities currently replaces the whole activities
state when the fetch returns, which can overwrite recently received SSE entries;
change it to merge the fetched items with the existing state instead of
wholesale replacing. In loadActivities (the useCallback) after computing
content.map(normalizeActivity) use setActivities with the functional updater
(setActivities(prev => { ... })) to combine normalized fetched items with prev,
deduplicate by a unique key (e.g., activity id), preserve desired ordering
(newest-first or existing order), and finally slice to MAX_ACTIVITY_LOGS;
reference normalizeActivity, setActivities, loadActivities, and
MAX_ACTIVITY_LOGS when implementing the merge/dedupe logic.
- Around line 235-248: The SSE handler only listens to the default "message"
event (onmessage) so named events like "newLog" from the server are ignored;
update the EventSource created with getDashboardActivitiesStreamUrl() to also
call eventSource.addEventListener('newLog', ...) and in that listener parse
event.data, then call setActivities(prev => mergeActivityLists(prev, [payload]))
(mirroring the existing onmessage logic) and keep the existing try/catch
behavior for non-JSON/heartbeat messages; ensure you also remove the
listener/close the EventSource where appropriate to avoid leaks.

In `@frontend/src/components/AdminDashbord/AdminDashbord.module.css`:
- Around line 1-5: Rename the directory and CSS file from AdminDashbord to
AdminDashboard (e.g., AdminDashbord.module.css → AdminDashboard.module.css) and
update all imports/usages that reference "AdminDashbord" (including any import
statements referring to AdminDashbord.module.css or the AdminDashbord
component/folder) to the new "AdminDashboard" name; ensure build/config
references (component folder names, route paths, and tests) are updated so the
.container class in AdminDashboard.module.css continues to be imported by the
correct component.

In `@frontend/src/components/AdminHome/AdminSidebar.jsx`:
- Around line 31-33: AdminSidebar.jsx enables menu items for /admin/posts,
/admin/attendance, /admin/points, /admin/tools but those routes are not defined
in App.jsx, causing navigation to blank pages; either (A) restore disabled: true
on the corresponding menu entries in AdminSidebar.jsx until routes exist, or (B)
add the missing admin routes in App.jsx (create and register route entries for
/admin/posts, /admin/attendance, /admin/points, /admin/tools pointing to the
appropriate page components e.g., PostsAdmin, AttendanceAdmin, PointsAdmin,
ToolsAdmin) so the sidebar links resolve correctly.

In `@frontend/src/components/AdminHome/MemberList.jsx`:
- Line 30: The component MemberList accesses styles.memberTableWrap but styles
can be undefined; update the component signature or usage to defensively handle
this by providing a default empty object for styles (e.g., in the parameter
destructuring of MemberList) or by using conditional access (optional chaining
or fallback) wherever styles.memberTableWrap is referenced, ensuring no runtime
error when styles is not passed.

In `@frontend/src/pages/Board.jsx`:
- Around line 229-246: The async fetch block that builds targetBoardIds and
calls fetchPostsByBoardIds must ignore stale responses: generate a unique
requestId (or incrementing fetchCounter) in the parent effect or handler,
capture it before awaiting fetchPostsByBoardIds(targetBoardIds), then after each
await check that the currentRequestId matches the captured one; only then call
setPostCacheByBoardId, setPosts (buildPostsFromCache), setSubBoardTabs and
setCurrentPage. Apply the same requestId check pattern to the other similar
blocks (the ones around lines 251-263 and 268-273) so only the latest request's
results update state.
- Around line 171-198: In fetchPostsByBoardIds, don't overwrite existing cache
with an empty array on individual getPosts failures; instead use
Promise.allSettled (or have each request return a { boardId, status } marker)
and only set nextCache[boardId] when the call succeeded (response.content is an
array); for rejected results leave the boardId out of nextCache (so existing
cache remains) or propagate the failure to the caller. Update the map of
responses->nextCache logic in fetchPostsByBoardIds and the boardApi.getPosts
error handling so failed requests are not converted into [] and thus do not
clobber existing cached posts.
- Around line 82-83: posts and postCacheByBoardId are acting as two sources of
truth causing optimistic updates to be lost when the UI rebuilds from cache;
consolidate the cache as the single source of truth or mirror mutations into the
cache. Fix by ensuring all mutation handlers that currently update posts (e.g.,
like/bookmark handlers that call setPosts) also update postCacheByBoardId via
setPostCacheByBoardId (apply the same update logic to the cached entry for the
affected board and post id), and change fetch/tab/search flows to derive the
visible posts from postCacheByBoardId instead of independently setting posts.
Apply the same changes in the other affected block (lines referenced 275-307) so
cache and visible state remain consistent.

In `@frontend/src/utils/adminDashboardApi.js`:
- Line 44: The getDashboardActivitiesStreamUrl constant currently returns a
relative path causing SSE to use the front-end origin; change it to build the
full URL using the same baseURL used by your axios instance (e.g., import the
base URL or a helper from frontend/src/utils/axios.js) and return
`${baseUrl}/api/admin/dashboard/activities/stream` (or replace the constant with
a shared exported base + path constant) so the SSE endpoint uses the same API
origin as axios; update getDashboardActivitiesStreamUrl to reference that shared
base constant or helper to avoid divergence.

---

Outside diff comments:
In `@frontend/src/pages/Board.jsx`:
- Around line 356-398: The current flow treats any error during getSubBoards as
a failure of createSubBoard; change it so createSubBoard is awaited inside its
own try/catch and on success you immediately close the modal, notify the user,
and proceed to refresh sub-boards in a separate try/catch; if getSubBoards
fails, log or show a non-blocking warning and fall back to using the created
response (created?.boardId and created?.boardName) to build tabs,
setSubBoardTabs, setActiveSubBoard, update postCacheByBoardId and setPosts (use
created as a source when subBoards is unavailable) so the created board is
reflected even if the refresh failed; specifically split the logic around
createSubBoard and getSubBoards calls and preserve use of symbols created,
getSubBoards, setSubBoardTabs, setActiveSubBoard, setPostCacheByBoardId,
buildPostsFromCache, and setPosts.

---

Nitpick comments:
In `@frontend/src/components/AdminHome/AdminSidebar.jsx`:
- Around line 99-106: Replace the manual isActive computation and string
interpolation with NavLink's built-in className callback: remove the isActive
variable and change the NavLink to pass a function to className that returns
`${styles.menuLink} ${isActive ? styles.menuLinkActive : ''}` (using the
callback's isActive argument) so active state is handled by NavLink for routes
like nested matches; update the NavLink instance that currently references
item.href and item.icon accordingly.
- Line 59: The optional chaining on pathname (const isDevSection =
pathname?.startsWith('/admin/dev')) is unnecessary because useLocation() always
returns an object with pathname; update the expression to use plain
pathname.startsWith('/admin/dev') to remove the redundant '?.' while keeping
behavior identical (refer to isDevSection, pathname, and useLocation to locate
the code).

In `@frontend/src/components/AdminHome/MemberList.jsx`:
- Around line 17-22: getRoleClassName currently falls back to styles.roleNormal
for unhandled labels; add an explicit branch for the '시스템 관리자' label so it
returns a dedicated style (e.g., styles.roleSysAdmin) instead of roleNormal.
Update the getRoleClassName function to check if roleLabel === '시스템 관리자' and
return the new styles property, and ensure the corresponding CSS/classname
(styles.roleSysAdmin) is defined where styles is declared.

In `@frontend/src/components/AdminRoute.jsx`:
- Around line 5-8: Extract the admin role list into a shared constant and import
it where needed: create a constants export (e.g., constants/roles.js) that
defines ADMIN_VISIBLE_ROLES or ADMIN_ROLES array containing 'PRESIDENT' and
'SYSTEM_ADMIN', then update the isAdminRole function in AdminRoute.jsx to
reference that shared constant instead of hardcoding, and update Sidebar.jsx to
import the same constant (refer to isAdminRole and ADMIN_VISIBLE_ROLES to locate
usage). Ensure the constant is exported as a plain array and update imports
accordingly.

In `@frontend/src/components/Sidebar.jsx`:
- Around line 101-103: currentPath, targetPath and nextPath are all assigned via
normalizeBoardPath(board.path) causing targetPath and nextPath to be duplicates;
either remove one and use a single variable (e.g., targetPath) or if they are
meant to represent different semantics, compute them differently and give them
clear names. Update the Sidebar.jsx code around the normalizeBoardPath calls:
keep currentPath = normalizeBoardPath(location.pathname) and replace the two
identical assignments with a single descriptive variable (or adjust the logic
that computes nextPath if it should differ), then update any downstream
references to use the consolidated name (currentPath, targetPath or nextPath) so
there are no redundant variables.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ac8a6107-7651-4ad2-854c-5242ac125822

📥 Commits

Reviewing files that changed from the base of the PR and between 1fa0964 and e122955.

📒 Files selected for processing (18)
  • frontend/src/App.jsx
  • frontend/src/components/AdminDashbord/AdminDashbord.jsx
  • frontend/src/components/AdminDashbord/AdminDashbord.module.css
  • frontend/src/components/AdminHome/AdminSidebar.jsx
  • frontend/src/components/AdminHome/MemberList.jsx
  • frontend/src/components/AdminHome/MembersPanel.jsx
  • frontend/src/components/AdminHome/RecentActivitiesPanel.jsx
  • frontend/src/components/AdminRoute.jsx
  • frontend/src/components/Board/CreateSubBoardModal.jsx
  • frontend/src/components/Sidebar.jsx
  • frontend/src/pages/AdminDashboard.jsx
  • frontend/src/pages/AdminDashboard.module.css
  • frontend/src/pages/AdminHome.module.css
  • frontend/src/pages/Board.jsx
  • frontend/src/utils/adminDashboardApi.js
  • frontend/src/utils/adminHomeData.js
  • frontend/src/utils/boardApi.js
  • frontend/src/utils/boardRoute.js

Comment thread frontend/src/components/AdminDashboard/AdminDashboard.jsx
Comment thread frontend/src/components/AdminDashboard/AdminDashboard.jsx
Comment thread frontend/src/components/AdminDashboard/AdminDashboard.module.css
Comment thread frontend/src/components/AdminHome/AdminSidebar.jsx
Comment thread frontend/src/components/AdminHome/MemberList.jsx Outdated
Comment thread frontend/src/pages/Board.jsx
Comment thread frontend/src/pages/Board.jsx
Comment thread frontend/src/pages/Board.jsx
Comment thread frontend/src/utils/adminDashboardApi.js
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (1)
frontend/src/components/AdminHome/MemberList.jsx (1)

40-42: 테이블 헤더에 scope="col"을 추가해 주세요.

지금도 동작은 하지만, 스크린리더가 열 헤더를 더 정확히 해석할 수 있습니다.

♿ 제안된 수정
-						<th>이름</th>
-						<th>권한</th>
-						<th>상태</th>
+						<th scope="col">이름</th>
+						<th scope="col">권한</th>
+						<th scope="col">상태</th>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/AdminHome/MemberList.jsx` around lines 40 - 42, In
the MemberList.jsx component, update the three table header <th> elements for
"이름", "권한", and "상태" to include scope="col" so screen readers can correctly
treat them as column headers; locate the <th> tags in the MemberList.jsx
render/return and add scope="col" to each header element.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/AdminDashboard/AdminDashboard.jsx`:
- Around line 327-329: The empty stat card markup in AdminDashboard.jsx (the
<article className={styles.statCard}> containing <div
className={styles.emptyPanelContent} />) renders an empty box and should be
cleaned up: either remove the empty card entirely or replace it with a clear
placeholder UI (e.g., a "No widget" or "Coming soon" message and optional
skeleton) and only render when there is real data; update the JSX in the
AdminDashboard component to conditionally render the statCard block based on
presence of the widget data or swap the emptyPanelContent for an explicit
placeholder component/message; repeat the same change for the other identical
empty block referenced in the file.
- Around line 275-286: The period <select> (controlled by periodDays and
setPeriodDays, with className styles.periodSelect and options from
PERIOD_OPTIONS) lacks an accessible name; add one by either giving the element
an id and rendering a corresponding <label htmlFor="...">Period</label> or by
adding an appropriate aria-label (e.g., aria-label="기간 필터" or "Period filter")
so screen readers can identify the control; ensure the chosen label text matches
the UI language and keep the disabled={loading} behavior unchanged.
- Around line 216-221: The catch block for the try that calls loadActivities
currently clears the activities via setActivities([]), which wipes SSE-delivered
logs on transient REST failures; instead, remove the call to setActivities([])
and only log or surface the error so the existing activities state is preserved.
Update the catch handling around loadActivities() (the try/catch that catches
activitiesError) to keep the current activities state and show the error
message/notification without calling setActivities.
- Around line 44-52: The formatDateLabel function uses new Date(value) which can
shift day across timezones for date-only strings; modify formatDateLabel to
first detect an ISO date-only string (e.g. match /^\d{4}-\d{2}-\d{2}$/), and if
it matches, split the string into [year, month, day] and return
`${month}/${day}` (ensuring month and day are two digits), otherwise fall back
to the existing Date parsing and validation logic (retain the current
Number.isNaN(date.getTime()) check and the '-' and String(value) fallbacks) so
non-date-only inputs keep current behavior.

In `@frontend/src/components/AdminHome/MemberList.jsx`:
- Around line 24-27: getStatusClassName currently branches on the display string
and treats any unknown label (including OUT/'탈퇴') as pending; change it to
branch on the canonical status code (e.g. member.status or the STATUS_LABELS
keys) instead of statusLabel so OUT is handled explicitly. Update
getStatusClassName to accept the raw status key (or map statusLabel back to the
key using STATUS_LABELS) and add an explicit case for 'OUT' mapping to the
correct class (styles.statusInactive or styles.statusOut if defined), and update
all calls (the usages around the other branch at lines 47-60) to pass the raw
status key so 탈퇴 members no longer fall back to styles.statusPending.

In `@frontend/src/pages/Board.jsx`:
- Around line 497-515: The optimistic rollback currently saves the entire
postCacheByBoardId snapshot and restores it on failure, which undoes concurrent
changes; instead capture the pre-update state of only the affected post inside
handleLike (e.g., read the existing post from postCacheByBoardId by postId),
then call updatePostInCache optimistically, and on catch restore just that
single post via updatePostInCache (or call a function to replace that post) or
trigger a re-fetch for the specific board; reference handleLike,
postCacheByBoardId, updatePostInCache and setPostCacheByBoardId and remove
restoring the whole snapshot in the catch block.
- Around line 386-394: The current merge for postCacheByBoardId builds a new
cache from the stale closure value of postCacheByBoardId (used in the block
around setPostCacheByBoardId and setCurrentPage), which can overwrite concurrent
updates (likes/bookmarks/fetches); change these updates to use the functional
updater form of setPostCacheByBoardId(prev => ...) and merge createdBoardId into
prev safely (e.g., check createdBoardId and only add an empty array if
prev[createdBoardId] is undefined), and do the same for the other occurrence
around lines 447-454 so all mutations reference the latest state rather than the
closed-over postCacheByBoardId.

---

Nitpick comments:
In `@frontend/src/components/AdminHome/MemberList.jsx`:
- Around line 40-42: In the MemberList.jsx component, update the three table
header <th> elements for "이름", "권한", and "상태" to include scope="col" so screen
readers can correctly treat them as column headers; locate the <th> tags in the
MemberList.jsx render/return and add scope="col" to each header element.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0c282ff3-7812-4375-b23f-7cb99cdee4dc

📥 Commits

Reviewing files that changed from the base of the PR and between e122955 and 92e0191.

📒 Files selected for processing (6)
  • frontend/src/components/AdminDashboard/AdminDashboard.jsx
  • frontend/src/components/AdminDashboard/AdminDashboard.module.css
  • frontend/src/components/AdminHome/MemberList.jsx
  • frontend/src/pages/AdminDashboard.jsx
  • frontend/src/pages/Board.jsx
  • frontend/src/utils/boardApi.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/pages/AdminDashboard.jsx
  • frontend/src/utils/boardApi.js

Comment thread frontend/src/components/AdminDashboard/AdminDashboard.jsx
Comment thread frontend/src/components/AdminDashboard/AdminDashboard.jsx
Comment thread frontend/src/components/AdminDashboard/AdminDashboard.jsx
Comment thread frontend/src/components/AdminDashboard/AdminDashboard.jsx
Comment thread frontend/src/components/AdminHome/MemberList.jsx
Comment thread frontend/src/pages/Board.jsx Outdated
Comment thread frontend/src/pages/Board.jsx
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/pages/Board.jsx`:
- Around line 536-554: handleBookmark currently saves the whole
postCacheByBoardId snapshot and restores it on error, which can clobber
concurrent per-post updates; instead, follow the per-post rollback used in
handleLike: capture the previous post state for the specific postId before
calling updatePostInCache (e.g., prevPost = postCacheByBoardId[postId] or
similar), optimistically toggle the post with updatePostInCache(postId, ...),
call boardApi.toggleBookmark(postId), and on error call
updatePostInCache(postId, () => prevPost) (and remove the
setPostCacheByBoardId(snapshot) whole-cache restore and related snapshot usage).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b89e7d2f-71c4-4e55-8176-65a793276e05

📥 Commits

Reviewing files that changed from the base of the PR and between 92e0191 and bfbe47e.

📒 Files selected for processing (2)
  • frontend/src/components/AdminDashboard/AdminDashboard.jsx
  • frontend/src/pages/Board.jsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/components/AdminDashboard/AdminDashboard.jsx

Comment thread frontend/src/pages/Board.jsx
Copy link
Copy Markdown
Contributor

@discipline24 discipline24 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다~

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

⚙️ [기능추가][관리자] 대시보드 페이지 구현

3 participants