관리자 페이지 신고탭 추가#270
Conversation
This reverts commit a7f87e5.
support admin page gamepreview
Walkthrough관리자 페이지(레이아웃·네비게이션), 신고 조회/상세/뮤테이션 API·훅, 게임 미리보기(Admin/User 분리), 관리자 테이블 UI, 차단/해제 모달 및 MSW 목핸들러와 관련 타입·유틸을 추가하고 기존 fetchClientFactory 및 이전 GamePreview를 제거했습니다. Changes
Sequence Diagram(s)sequenceDiagram
actor Admin as 관리자
participant Browser as 브라우저
participant AdminLayout as AdminLayout
participant AuthAPI as 인증 API
participant MockServer as MSW/백엔드
Admin->>Browser: /admin 접근
Browser->>AdminLayout: 마운트 -> validateSession()
AdminLayout->>AuthAPI: GET /users/auth/me
AuthAPI->>MockServer: 세션 확인
alt 비인증 또는 권한없음
MockServer-->>AuthAPI: 401 / null
AdminLayout-->>Browser: "/"로 리다이렉트
else ADMIN
MockServer-->>AuthAPI: UserSession(role="ADMIN")
AdminLayout-->>Browser: 관리자 네비게이션 및 children 렌더
end
sequenceDiagram
actor Admin as 관리자
participant ReportsPage as AdminReportsPage
participant useAdminReports as useAdminReports
participant ReportAPI as Report API
participant Overlay as Overlay Kit
participant Preview as GamePreviewAdmin
Admin->>ReportsPage: 신고 페이지 접속
ReportsPage->>useAdminReports: getAdminReports(page)
useAdminReports->>ReportAPI: GET /admin/games
ReportAPI-->>useAdminReports: reports list
ReportsPage-->>Admin: ReportedGamesTable 렌더
Admin->>ReportedGamesTable: 행 클릭(reportId)
ReportedGamesTable->>Preview: openAdminPreview(reportId)
Preview->>ReportAPI: GET /admin/games/{reportId}
ReportAPI-->>Preview: reportDetail
Preview->>Overlay: overlay.open(GamePreviewAdmin)
Admin->>Preview: "게임 삭제" 클릭
Preview->>ReportAPI: POST /admin/games/delete {reportId, status: DELETE_GAME}
ReportAPI-->>Preview: success
Preview->>useAdminReports: invalidateQuery("adminReports")
useAdminReports->>ReportAPI: GET /admin/games (refetch)
ReportAPI-->>useAdminReports: updated list
Preview->>Overlay: overlay.close()
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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 |
|
❌ 테스트 실패 테스트가 실패했습니다. Actions 로그를 확인해주세요. |
|
❌ 테스트 실패 테스트가 실패했습니다. Actions 로그를 확인해주세요. |
There was a problem hiding this comment.
Actionable comments posted: 13
🤖 Fix all issues with AI agents
In `@service/app/src/entities/auth/api/validateSession.ts`:
- Around line 3-8: The current validateSession implementation calls
fetchClient.get<null> which discards the session type and never returns null on
failure; change the generic to fetchClient.get<UserSessionResponse> and add a
failure branch (check response.ok or catch network/errors) so that on non-2xx
responses or exceptions the function returns null; update validateSession to
parse and return the UserSessionResponse on success and null on failure
(referencing validateSession, fetchClient.get, and UserSessionResponse).
In `@service/app/src/entities/game/hooks/useGameReportDetail.tsx`:
- Around line 58-67: The GamePreviewAdmin prop is receiving a misspelled field
(data.quetionCount) so the displayed question count is empty; in
useGameReportDetail (where GamePreviewAdmin is returned) change the prop
assignment to pass data.questionCount instead of data.quetionCount so the
correct value flows into the GamePreviewAdmin component (verify the prop name
questionCount and the data property questionCount align).
In `@service/app/src/entities/game/ui/GamePreview/gamePreviewReportTable.tsx`:
- Around line 26-106: The outer row containers are missing role="row" (you used
role="table" elsewhere), so add role="row" to each of the three top-level divs
that wrap each row (the divs starting with className "flex w-full items-center
..." — e.g., the blocks for 제작자, 신고자, 항목) so the row semantics match the
existing role="table" and the inner headers/cells (role="rowheader" and
role="cell") are correctly associated.
In `@service/app/src/entities/report/api/getReportDetail.ts`:
- Around line 5-10: The function getReportDetail uses a .then() chain on
response.json(), violating the async/await guideline; update it to await the
JSON parsing instead: after calling fetchClient.get in getReportDetail, await
response.json() into a variable (e.g., const res = await response.json()) and
return res.data, removing the .then() usage so the function consistently uses
async/await.
In `@service/app/src/entities/report/model/types.ts`:
- Around line 58-65: The ReportDetailData interface has a typo: the field is
named quetionCount but should be questionCount; update the property name in the
ReportDetailData interface to questionCount and then update all
references/usages (tests, serializers, mappers, consumers) that read or write
ReportDetailData so they use ReportDetailData.questionCount instead of
quetionCount to ensure the API/consumer fields match.
In `@service/app/src/entities/report/utils/formatReportDate.ts`:
- Around line 1-6: formatReportDate currently uses local-date getters
(getFullYear/getMonth/getDate) which can shift the day in non-UTC timezones;
change it to use UTC getters: getUTCFullYear, getUTCMonth (plus 1), and
getUTCDate, and keep the existing zero-padding logic so the returned string is
consistently YYYY-MM-DD in UTC.
In `@service/app/src/mocks/handlers/admin.ts`:
- Around line 39-50: The page parsing currently sets page to parseInt(pageParam,
10) without handling NaN which can break pagination; change the logic around
pageParam/parseInt so you parse into a temporary variable (e.g., const parsed =
parseInt(pageParam, 10)) and then set page = pageParam ? (Number.isNaN(parsed) ?
0 : Math.max(0, parsed)) : 0 so NaN falls back to 0 and negative values are
clamped; update references to page (used with PAGE_SIZE, startIndex, endIndex,
hasNext, totalPages) accordingly.
In `@service/app/src/shared/ui/admin-table/AdminTableRoot.tsx`:
- Around line 1-3: Rename the file from AdminTableRoot.tsx to camelCase (e.g.,
adminTableRoot.tsx) and update all imports and barrel exports that reference
AdminTableRoot to the new filename; keep the internal React component/identifier
(AdminTableRoot) PascalCase inside the file but change all import paths (and any
export lists) that used "AdminTableRoot" as a module path to the new
"adminTableRoot" path so module resolution stays correct.
In `@service/app/src/shared/ui/admin-table/AdminTableRow.tsx`:
- Line 1: Rename the file AdminTableRow.tsx to adminTableRow.tsx and update all
imports and barrel exports that reference "AdminTableRow" (e.g., any import
paths or index/barrel files exporting AdminTableRow) to use the new camelCase
filename "adminTableRow". Ensure the component's exported symbol name (if
default or named) remains unchanged so no other code changes are needed beyond
adjusting the import paths and barrel entries.
In `@service/app/src/shared/ui/admin-table/AdminTableText.tsx`:
- Around line 1-32: Rename the component file from AdminTableText.tsx to
adminTableText.tsx to follow camelCase file naming; update all imports/usages of
AdminTableText (e.g., import/exports in barrels and any files importing the
component) to reference the new filename while keeping the exported symbol name
AdminTableText unchanged, and verify barrel files (index.ts/exports) and any
path-based imports are updated so builds/tests pass.
In `@service/app/src/shared/ui/admin-table/index.ts`:
- Line 5: index.ts re-exports a missing module "useRowSelection", causing
TS2307; either add the missing module file (implementing the hook named
useRowSelection), correct the import path/casing in the export statement in
service/app/src/shared/ui/admin-table/index.ts to match the actual filename, or
remove the export if the hook is unused. Locate the export line "export * from
\"./useRowSelection\"" in index.ts and then (a) add a corresponding
useRowSelection.ts(x) that exports the hook, or (b) fix the relative
path/filename capitalization to the real file, or (c) delete this export when
there are no consumers.
In `@service/app/src/widgets/admin/reported-games-table/ui/ReportedGameRow.tsx`:
- Around line 13-20: The ReportedGameRow component's AdminTableRow is focusable
but doesn't handle keyboard activation; update ReportedGameRow to call the same
onClick handler when Enter or Space is pressed (e.g., implement an onKeyDown
that checks for "Enter" or " " / "Spacebar" and invokes onClick?.(item)), add
role="button" to AdminTableRow if it isn't a semantic button, and ensure you
preventDefault for Space to avoid page scroll; keep aria-label and tabIndex
as-is so screen reader and keyboard users can activate the row.
In
`@service/app/src/widgets/admin/reported-games-table/ui/ReportedGamesTable.tsx`:
- Around line 1-52: The filename uses PascalCase instead of the project's
camelCase filename convention; rename the file containing the ReportedGamesTable
React component to reportedGamesTable.tsx and update any imports referencing
ReportedGamesTable to use the new filename (e.g., imports of ReportedGamesTable
or ReportedGameRow where this component is imported). Ensure the exported
component name ReportedGamesTable remains unchanged, update any related
barrel/index exports if present, and run a quick typecheck to catch any stale
import paths.
🧹 Nitpick comments (14)
service/app/src/entities/report/api/updateAdminReportStatus.ts (1)
5-10: 엔드포인트 명명은 개선 가능하지만 기능은 정상 작동합니다.
updateAdminReportStatus는 payload의status필드("IGNORE_REPORT" 또는 "DELETE_GAME")에 따라 두 가지 액션을 모두 처리합니다. 백엔드 mock handler가 두 경우를 모두 정상 처리하고 있으며, hook에서도 ignore와 delete 모두에 동일하게 사용하고 있습니다. 따라서 무시/차단 기능이 깨지지 않습니다.다만
/admin/games/delete엔드포인트 명이 여러 액션을 담당한다는 의도를 드러내지 못해, 더 명확한 명칭(예:/admin/games/report-action)으로 개선하는 것을 검토해볼 수 있습니다.service/app/src/widgets/admin/reported-games-table/ui/ReportedGameRow.tsx (1)
1-1: 파일명이 PascalCase입니다 — camelCase로 변경 필요
코딩 가이드라인에 따라ReportedGameRow.tsx는reportedGameRow.tsx처럼 camelCase로 맞춰 주세요. As per coding guidelines, ...service/app/src/entities/game/ui/GamePreview/gamePreviewShell.tsx (2)
20-25:onOpenChange핸들러가 전달받은 상태 값을 무시합니다.
onOpenChange는 다이얼로그 상태 변경 시boolean값을 전달받습니다. 현재 구현에서는 이 값을 무시하고 항상onClose를 호출하므로, 다이얼로그가 열리는 경우에도onClose가 호출될 수 있습니다.♻️ 제안하는 수정사항
- const handleClose = () => { - onClose?.() - } - return ( - <Dialog open={isOpen} onOpenChange={handleClose}> + <Dialog open={isOpen} onOpenChange={(open) => !open && onClose?.()}>
26-28:cn()유틸리티 사용을 권장합니다.템플릿 리터럴 대신
cn()유틸리티를 사용하면 클래스 병합이 더 안전하고 가독성이 향상됩니다. 특히 외부에서 전달된className이 기존 스타일을 오버라이드해야 할 때 유용합니다.service/app/src/entities/game/ui/GamePreview/gamePreviewQuestionList.tsx (1)
27-28:blurDataURL을 상수로 추출하는 것을 고려해주세요.긴 base64 문자열이 JSX 내에 인라인으로 포함되어 있어 가독성이 저하됩니다. 상수나 공용 유틸리티로 추출하면 재사용성과 가독성이 향상됩니다.
service/app/src/entities/report/hooks/useAdminReportMutations.ts (2)
4-4:useQueryClient()훅 사용을 권장합니다.직접 import한
queryClient를 사용하는 대신useQueryClient()훅을 사용하는 것이 React Query의 권장 패턴입니다. 이 방식은 SSR 환경에서 더 안전하고 테스트 시 모킹이 용이합니다.♻️ 제안하는 수정사항
-import { useMutation } from "@tanstack/react-query" +import { useMutation, useQueryClient } from "@tanstack/react-query" import { blockUsers } from "@/entities/suspension/api/blockUsers" -import { queryClient } from "@/shared/lib/queryClient" import { updateAdminReportStatus } from "../api/updateAdminReportStatus" export const useAdminReportMutations = () => { + const queryClient = useQueryClient() + const ignoreReportMutation = useMutation({
17-23: 게임 삭제 후reportDetail쿼리도 무효화가 필요할 수 있습니다.
deleteGameMutation성공 시adminReports만 무효화하고 있습니다. 신고 상세 팝업이 열린 상태에서 게임을 삭제하면reportDetail데이터도 갱신이 필요할 수 있습니다.ignoreReportMutation도 동일한 상황입니다.♻️ 제안하는 수정사항
const deleteGameMutation = useMutation({ mutationFn: (reportId: number) => updateAdminReportStatus({ status: "DELETE_GAME", reportId }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["adminReports"] }) + queryClient.invalidateQueries({ queryKey: ["reportDetail"] }) }, })service/app/src/entities/report/api/getAdminReports.ts (1)
9-17: Promise 체인 대신 async/await 사용 권장이미 async 함수이므로
response.json()결과를await로 처리하면 흐름이 단순해집니다. 코딩 가이드라인에 따라.♻️ 개선안
const response = await fetchClient.get<AdminReportsResponse>( `admin/games${queryString}`, ) - return response.json().then((res) => res.data) + const { data } = await response.json() + return dataservice/app/src/app/admin/layout.tsx (1)
18-30: 세션 검증 로직을 async/await로 정리Promise 체인 대신 비동기 함수로 처리하면 흐름이 단순해지고 규칙에도 맞습니다. 코딩 가이드라인에 따라.
♻️ 개선안
useEffect(() => { - validateSession() - .then((user) => { - if (!user || user.data?.role !== "ADMIN") { - router.replace("/") - return - } - setChecked(true) - }) - .catch(() => { - router.replace("/") - }) + const checkSession = async () => { + try { + const user = await validateSession() + if (!user || user.data?.role !== "ADMIN") { + router.replace("/") + return + } + setChecked(true) + } catch { + router.replace("/") + } + } + void checkSession() }, [router])service/app/src/shared/ui/admin-table/AdminTableGroup.tsx (1)
1-2: 파일명 PascalCase → camelCase 정리 필요파일명이
AdminTableGroup.tsx로 PascalCase입니다.adminTableGroup.tsx로 변경하고 배럴/임포트를 함께 갱신하세요. 코딩 가이드라인에 따라.service/app/src/app/admin/reports/page.tsx (1)
4-12: Zustand 사용 규칙 준수 여부 확인 필요 (Line 4-12)
가이드라인에 “클라이언트 상태는 Zustand 사용”이 명시되어 있는데, 여기서는useState로 페이지 상태를 관리하고 있습니다. 로컬 상태 예외인지 확인하고, 필요하다면 Zustand로 이전하거나 예외 기준을 문서화해 주세요.service/app/src/shared/ui/admin-table/AdminTableRoot.tsx (1)
4-20: 접근성: aria-label 기본값 또는 필수화 필요 (Line 4-20)
role="table"요소에 접근 가능한 이름이 없으면 스크린리더 접근성이 떨어질 수 있습니다.ariaLabel을 필수로 하거나 기본값을 부여하는 쪽을 권장합니다.♿️ 제안 수정안
-interface AdminTableRootProps { +interface AdminTableRootProps { children: ReactNode className?: string - ariaLabel?: string + ariaLabel?: string } export const AdminTableRoot = ({ children, className, - ariaLabel, + ariaLabel = "관리자 테이블", }: AdminTableRootProps) => {service/app/src/mocks/data/admin.ts (1)
42-81: 전역 Math.random 덮어쓰기는 피하세요.
동기 함수라도 전역 변경은 테스트 간 간섭 위험이 있습니다. 로컬 PRNG 함수로 대체하는 편이 안전합니다.♻️ 제안 변경
export const generateMockAdminReports = ( count: number, seed?: number, ): AdminReport[] => { - let originalRandom: (() => number) | undefined - - if (seed !== undefined) { - originalRandom = Math.random - let currentSeed = seed - Math.random = () => { - const x = Math.sin(currentSeed++) * 10000 - return x - Math.floor(x) - } - } + let random = Math.random + if (seed !== undefined) { + let currentSeed = seed + random = () => { + const x = Math.sin(currentSeed++) * 10000 + return x - Math.floor(x) + } + } const result: AdminReport[] = [] for (let i = 0; i < count; i++) { - const randomIndex = Math.floor(Math.random() * gameNames.length) - const creatorIndex = Math.floor(Math.random() * creatorNames.length) - const reporterIndex = Math.floor(Math.random() * reporterNames.length) - const daysAgo = Math.floor(Math.random() * 30) + const randomIndex = Math.floor(random() * gameNames.length) + const creatorIndex = Math.floor(random() * creatorNames.length) + const reporterIndex = Math.floor(random() * reporterNames.length) + const daysAgo = Math.floor(random() * 30) @@ - if (originalRandom) { - Math.random = originalRandom - } - return result }shared/design/src/components/pagination/pagination.tsx (1)
49-88: currentPage 범위 보호를 추가해주세요.상위에서 범위를 벗어난 값이 들어오면 계산/핸들러가 일관되지 않을 수 있어, 내부에서 최소/최대 클램프를 두는 편이 안전합니다.
♻️ 제안 변경
+ const safeCurrentPage = Math.min( + Math.max(currentPage, 1), + totalPages, + ) + const handlePrev = () => { - if (currentPage > 1) { - onPageChange(currentPage - 1) + if (safeCurrentPage > 1) { + onPageChange(safeCurrentPage - 1) } } const handleNext = () => { - if (currentPage < totalPages) { - onPageChange(currentPage + 1) + if (safeCurrentPage < totalPages) { + onPageChange(safeCurrentPage + 1) } } const handlePageClick = (page: number) => { - if (page !== currentPage) { + if (page !== safeCurrentPage) { onPageChange(page) } } const getVisiblePages = () => { const pages: number[] = [] const half = Math.floor(maxVisible / 2) - let start = Math.max(1, currentPage - half) + let start = Math.max(1, safeCurrentPage - half) const end = Math.min(totalPages, start + maxVisible - 1)
| @@ -0,0 +1,29 @@ | |||
| import { cn } from "@ject-5-fe/design/utils/cn" | |||
There was a problem hiding this comment.
파일명이 camelCase 규칙을 위반합니다.
AdminTableRow.tsx는 PascalCase라서 규칙 위반입니다. adminTableRow.tsx로 변경하고 배럴/임포트 경로도 함께 갱신해 주세요. 코딩 가이드라인에 따라, 반드시 camelCase 파일명을 사용해야 합니다.
🤖 Prompt for AI Agents
In `@service/app/src/shared/ui/admin-table/AdminTableRow.tsx` at line 1, Rename
the file AdminTableRow.tsx to adminTableRow.tsx and update all imports and
barrel exports that reference "AdminTableRow" (e.g., any import paths or
index/barrel files exporting AdminTableRow) to use the new camelCase filename
"adminTableRow". Ensure the component's exported symbol name (if default or
named) remains unchanged so no other code changes are needed beyond adjusting
the import paths and barrel entries.
| import { cn } from "@ject-5-fe/design/utils/cn" | ||
| import type { ReactNode } from "react" | ||
|
|
||
| type AdminTableTextTone = "header" | "body" | ||
|
|
||
| interface AdminTableTextProps { | ||
| children: ReactNode | ||
| className?: string | ||
| tone?: AdminTableTextTone | ||
| } | ||
|
|
||
| export const AdminTableText = ({ | ||
| children, | ||
| className, | ||
| tone = "body", | ||
| }: AdminTableTextProps) => { | ||
| const header = | ||
| "typography-heading-md-medium text-text-interactive-secondary-hovered" | ||
| const body = "typography-heading-md-medium text-text-interactive-input-filled" | ||
|
|
||
| return ( | ||
| <span | ||
| className={cn( | ||
| "min-w-0 truncate", | ||
| tone === "header" ? header : body, | ||
| className, | ||
| )} | ||
| role={tone === "header" ? "columnheader" : "cell"} | ||
| > | ||
| {children} | ||
| </span> | ||
| ) |
There was a problem hiding this comment.
파일명이 camelCase 규칙을 위반합니다.
가이드라인상 파일명은 camelCase여야 합니다. AdminTableText.tsx → adminTableText.tsx로 변경하고 관련 import/barrel도 함께 수정해 주세요.
🤖 Prompt for AI Agents
In `@service/app/src/shared/ui/admin-table/AdminTableText.tsx` around lines 1 -
32, Rename the component file from AdminTableText.tsx to adminTableText.tsx to
follow camelCase file naming; update all imports/usages of AdminTableText (e.g.,
import/exports in barrels and any files importing the component) to reference
the new filename while keeping the exported symbol name AdminTableText
unchanged, and verify barrel files (index.ts/exports) and any path-based imports
are updated so builds/tests pass.
| export const ReportedGameRow = ({ item, onClick }: ReportedGameRowProps) => { | ||
| return ( | ||
| <AdminTableRow | ||
| className="cursor-pointer gap-72" | ||
| onClick={() => onClick?.(item)} | ||
| aria-label={`게임명 ${item.title} 제작자 ${item.creatorName} 신고자 ${item.reporterName} 신고일자 ${item.reportedAt} 처리 여부 ${item.statusLabel}`} | ||
| tabIndex={0} | ||
| > |
There was a problem hiding this comment.
키보드로 행 클릭이 불가합니다 (접근성).
tabIndex는 있지만 Enter/Space 처리 없어서 키보드 사용자에게는 동작하지 않습니다. 키보드 활성화를 추가해 주세요.
🛠️ 제안 수정
- <AdminTableRow
- className="cursor-pointer gap-72"
- onClick={() => onClick?.(item)}
+ <AdminTableRow
+ className={onClick ? "cursor-pointer gap-72" : "gap-72"}
+ onClick={() => onClick?.(item)}
+ onKeyDown={(event) => {
+ if (!onClick) return
+ if (event.key === "Enter" || event.key === " ") {
+ event.preventDefault()
+ onClick(item)
+ }
+ }}
aria-label={`게임명 ${item.title} 제작자 ${item.creatorName} 신고자 ${item.reporterName} 신고일자 ${item.reportedAt} 처리 여부 ${item.statusLabel}`}
- tabIndex={0}
+ tabIndex={onClick ? 0 : -1}
>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const ReportedGameRow = ({ item, onClick }: ReportedGameRowProps) => { | |
| return ( | |
| <AdminTableRow | |
| className="cursor-pointer gap-72" | |
| onClick={() => onClick?.(item)} | |
| aria-label={`게임명 ${item.title} 제작자 ${item.creatorName} 신고자 ${item.reporterName} 신고일자 ${item.reportedAt} 처리 여부 ${item.statusLabel}`} | |
| tabIndex={0} | |
| > | |
| export const ReportedGameRow = ({ item, onClick }: ReportedGameRowProps) => { | |
| return ( | |
| <AdminTableRow | |
| className={onClick ? "cursor-pointer gap-72" : "gap-72"} | |
| onClick={() => onClick?.(item)} | |
| onKeyDown={(event) => { | |
| if (!onClick) return | |
| if (event.key === "Enter" || event.key === " ") { | |
| event.preventDefault() | |
| onClick(item) | |
| } | |
| }} | |
| aria-label={`게임명 ${item.title} 제작자 ${item.creatorName} 신고자 ${item.reporterName} 신고일자 ${item.reportedAt} 처리 여부 ${item.statusLabel}`} | |
| tabIndex={onClick ? 0 : -1} | |
| > |
🤖 Prompt for AI Agents
In `@service/app/src/widgets/admin/reported-games-table/ui/ReportedGameRow.tsx`
around lines 13 - 20, The ReportedGameRow component's AdminTableRow is focusable
but doesn't handle keyboard activation; update ReportedGameRow to call the same
onClick handler when Enter or Space is pressed (e.g., implement an onKeyDown
that checks for "Enter" or " " / "Spacebar" and invokes onClick?.(item)), add
role="button" to AdminTableRow if it isn't a semantic button, and ensure you
preventDefault for Space to avoid page scroll; keep aria-label and tabIndex
as-is so screen reader and keyboard users can activate the row.
| import type { ReportedGame } from "@/entities/report/model/types" | ||
| import { | ||
| AdminTableGroup, | ||
| AdminTableRoot, | ||
| AdminTableRow, | ||
| AdminTableText, | ||
| } from "@/shared/ui/admin-table" | ||
|
|
||
| import { ReportedGameRow } from "./ReportedGameRow" | ||
|
|
||
| interface ReportedGamesTableProps { | ||
| items: ReportedGame[] | ||
| onRowClick?: (item: ReportedGame) => void | ||
| } | ||
|
|
||
| export const ReportedGamesTable = ({ | ||
| items, | ||
| onRowClick, | ||
| }: ReportedGamesTableProps) => { | ||
| return ( | ||
| <AdminTableRoot ariaLabel="신고접수 테이블"> | ||
| <AdminTableRow | ||
| variant="header" | ||
| className="gap-72" | ||
| aria-label="신고접수 테이블 헤더" | ||
| > | ||
| <AdminTableGroup className="w-full gap-56 md:min-w-[400px] lg:min-w-[800px]"> | ||
| <AdminTableText tone="header" className="min-w-[45px]"> | ||
| 번호 | ||
| </AdminTableText> | ||
| <AdminTableText tone="header">게임명</AdminTableText> | ||
| </AdminTableGroup> | ||
|
|
||
| <AdminTableGroup className="w-full gap-60"> | ||
| <AdminTableText tone="header">제작자</AdminTableText> | ||
| <AdminTableText tone="header">신고자</AdminTableText> | ||
| </AdminTableGroup> | ||
|
|
||
| <AdminTableGroup className="w-full gap-24"> | ||
| <AdminTableText tone="header" className="min-w-[200px]"> | ||
| 신고일자 | ||
| </AdminTableText> | ||
| <AdminTableText tone="header">처리 여부</AdminTableText> | ||
| </AdminTableGroup> | ||
| </AdminTableRow> | ||
|
|
||
| {items.map((item) => ( | ||
| <ReportedGameRow key={item.id} item={item} onClick={onRowClick} /> | ||
| ))} | ||
| </AdminTableRoot> | ||
| ) | ||
| } |
There was a problem hiding this comment.
파일명은 camelCase 규칙을 따라야 합니다.
현재 파일명 ReportedGamesTable.tsx는 PascalCase입니다. 가이드에 맞게 reportedGamesTable.tsx로 변경해 주세요.
🤖 Prompt for AI Agents
In `@service/app/src/widgets/admin/reported-games-table/ui/ReportedGamesTable.tsx`
around lines 1 - 52, The filename uses PascalCase instead of the project's
camelCase filename convention; rename the file containing the ReportedGamesTable
React component to reportedGamesTable.tsx and update any imports referencing
ReportedGamesTable to use the new filename (e.g., imports of ReportedGamesTable
or ReportedGameRow where this component is imported). Ensure the exported
component name ReportedGamesTable remains unchanged, update any related
barrel/index exports if present, and run a quick typecheck to catch any stale
import paths.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@service/app/src/app/admin/reports/__tests__/reports.spec.ts`:
- Around line 122-132: The assertion is checking the wrong row after deleting
the clicked game: update the "삭제되지 않은 게임..." test to verify the same row you
clicked (use reportedGameRows.first() or reportedGameRows.nth(0) instead of
reportedGameRows.nth(1)) so it matches clickReportedGameRow(0); also ensure you
await any state update after clickDeleteGame (the existing await
expect(pom.reportDetail).not.toBeVisible() can remain) before asserting the
row's aria-label to avoid flakiness.
🧹 Nitpick comments (2)
service/app/src/app/admin/reports/__tests__/reports.spec.ts (2)
14-20: 주석 처리된 테스트 코드를 추적하거나 구현하세요.관리자가 아닌 로그인 사용자에 대한 리다이렉트 테스트가 주석 처리되어 있습니다. RBAC(역할 기반 접근 제어) 검증에 중요한 테스트이므로, 구현 계획이 있다면 이슈로 추적하고 주석 코드는 제거하는 것이 좋습니다.
이 테스트 케이스 구현을 위한 이슈를 생성해 드릴까요?
24-24: 관리자 테스트에admin.json과 같이 역할을 명확히 나타내는 인증 상태 파일명을 사용하세요.현재 관리자 페이지 테스트들이 모두
playwright/.auth/user.json을 사용하고 있는데, 파일명이 일반 사용자를 나타내어 혼동을 초래할 수 있습니다. 특히 코드의 17번 줄 주석에서 "USER role로 로그인 상태 설정 필요"라고 명시되어 있어, 향후 USER 역할과 ADMIN 역할을 분리할 때admin.json으로 이름을 변경하면 역할 구분을 더 명확하게 할 수 있습니다.
|
Deploy preview for re-creation ready! ✅ Preview Built with commit 318b593. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@service/app/src/app/admin/reports/__tests__/reports.spec.ts`:
- Around line 5-21: The commented-out authorization test inside the
reports.spec.ts ("로그인 사용자라도 ADMIN이 아닌 경우 홈으로 리다이렉트 되어야 한다") should not remain as
a raw comment; either (A) open a tracked issue referencing that test and remove
the commented block, or (B) restore it as a managed test by converting the
comment to a skipped or TODO test (e.g., use test.skip with a short reason) or
implement it properly using your auth helpers (e.g., loginAsUser /
setAuthCookie) then assert navigation from page.goto("/admin/reports") to
page.waitForURL("/"); update the test suite around the "인증 및 권한" describe block
accordingly so the missing-case is discoverable and actionable.
| test.describe("신고접수 페이지", () => { | ||
| test.describe("인증 및 권한", () => { | ||
| test("비로그인 사용자는 홈으로 리다이렉트 되어야 한다", async ({ | ||
| page, | ||
| }) => { | ||
| await page.goto("/admin/reports") | ||
| await page.waitForURL("/") | ||
| }) | ||
|
|
||
| // test("로그인 사용자라도 ADMIN이 아닌 경우 홈으로 리다이렉트 되어야 한다", async ({ | ||
| // page, | ||
| // }) => { | ||
| // // TODO: USER role로 로그인 상태 설정 필요 | ||
| // await page.goto("/admin/reports") | ||
| // await page.waitForURL("/") | ||
| // }) | ||
| }) |
There was a problem hiding this comment.
주석 처리된 권한 테스트는 추적이 어렵습니다.
해당 케이스는 주석 블록 대신 이슈로 등록하거나 테스트 형태로 관리해 주세요. 필요하시면 구현 지원 가능합니다.
🤖 Prompt for AI Agents
In `@service/app/src/app/admin/reports/__tests__/reports.spec.ts` around lines 5 -
21, The commented-out authorization test inside the reports.spec.ts ("로그인 사용자라도
ADMIN이 아닌 경우 홈으로 리다이렉트 되어야 한다") should not remain as a raw comment; either (A)
open a tracked issue referencing that test and remove the commented block, or
(B) restore it as a managed test by converting the comment to a skipped or TODO
test (e.g., use test.skip with a short reason) or implement it properly using
your auth helpers (e.g., loginAsUser / setAuthCookie) then assert navigation
from page.goto("/admin/reports") to page.waitForURL("/"); update the test suite
around the "인증 및 권한" describe block accordingly so the missing-case is
discoverable and actionable.
|
4354cb9 이 커밋은 reset하는게 좋을것 같습니다. 이부분이랑 테스트코드 연관된부분은 제가 수정할게요 그리고 aria태그나 role은 정말 최소한만 사용하는걸로 하면 좋을것 같습니다. html 시멘틱태그를 우선 사용하고, 모르는 부분은 그냥 안 건드는게 접근성 측면에서는 더 좋을 것 같습니다. 만약 테스트에 필요하다면 차라리 data-test-id같은 data attribute를 심는게 aria태그 오용보다는 더 좋을 것 같습니다. |
📝 설명
🛠️ 주요 변경 사항
리뷰 시 고려해야 할 사항
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.