[FE] 20260308 #267 기능추가관리자 대시보드 페이지 구현#289
Hidden character warning
Conversation
Walkthrough관리자 대시보드 페이지와 관련 컴포넌트/스타일이 추가되었고, 보드 페이지에 클라이언트 캐시와 경로 정규화 유틸이 도입되었습니다. 여러 관리자 UI(사이드바, 멤버 테이블, 활동 목록)와 API 유틸이 추가·수정되었습니다. Changes
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
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: 로그/뷰 갱신
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 inApp.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.
There was a problem hiding this comment.
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-dom의NavLink는 이미 내장된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:12의ADMIN_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: 중복 변수 할당
targetPath와nextPath가 동일한 값으로 할당됩니다. 가독성을 위해 하나로 통합하거나, 의도가 다르다면 명확히 구분하는 것이 좋습니다.♻️ 제안된 수정
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
📒 Files selected for processing (18)
frontend/src/App.jsxfrontend/src/components/AdminDashbord/AdminDashbord.jsxfrontend/src/components/AdminDashbord/AdminDashbord.module.cssfrontend/src/components/AdminHome/AdminSidebar.jsxfrontend/src/components/AdminHome/MemberList.jsxfrontend/src/components/AdminHome/MembersPanel.jsxfrontend/src/components/AdminHome/RecentActivitiesPanel.jsxfrontend/src/components/AdminRoute.jsxfrontend/src/components/Board/CreateSubBoardModal.jsxfrontend/src/components/Sidebar.jsxfrontend/src/pages/AdminDashboard.jsxfrontend/src/pages/AdminDashboard.module.cssfrontend/src/pages/AdminHome.module.cssfrontend/src/pages/Board.jsxfrontend/src/utils/adminDashboardApi.jsfrontend/src/utils/adminHomeData.jsfrontend/src/utils/boardApi.jsfrontend/src/utils/boardRoute.js
There was a problem hiding this comment.
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
📒 Files selected for processing (6)
frontend/src/components/AdminDashboard/AdminDashboard.jsxfrontend/src/components/AdminDashboard/AdminDashboard.module.cssfrontend/src/components/AdminHome/MemberList.jsxfrontend/src/pages/AdminDashboard.jsxfrontend/src/pages/Board.jsxfrontend/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
There was a problem hiding this comment.
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
📒 Files selected for processing (2)
frontend/src/components/AdminDashboard/AdminDashboard.jsxfrontend/src/pages/Board.jsx
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/components/AdminDashboard/AdminDashboard.jsx
관리자 대시보드 api 연결 및 게시판 버그 수정
Summary by CodeRabbit
새로운 기능
개선 사항