Feature#335 관리자 대시보드 페이지 추가#362
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. WalkthroughAdds typed API clients and React Query hooks for dashboard info and student list, introduces StudentsPage with search/sort/pagination, replaces pagination range logic with a new usePagination hook, wires dashboard stats to backend, updates routes and navigation, removes StudentTable and LoginModal, and adds common hooks barrel exports. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Admin as Admin User
participant UI as StudentsPage
participant Hooks as Search/Sort/Pagination Hooks
participant Query as React Query
participant API as /admin/students
Admin->>UI: Load /students
UI->>Hooks: init filters, sort, page
UI->>Query: useQuery(["student", params], getStudentList)
Query->>API: GET /admin/students?name&phone&order&page&size
API-->>Query: 200 OK (paginated students)
Query-->>UI: data { content, totalPages, ... }
UI-->>Admin: Render table + pagination
Admin->>UI: Change filters/sort/page
UI->>Hooks: update state
UI->>Query: refetch with new params
API-->>Query: 200 OK
Query-->>UI: updated data
UI-->>Admin: Updated table
sequenceDiagram
autonumber
actor Admin as Admin User
participant Dash as StatSection
participant Query as React Query
participant API as /admin/dashboard
Admin->>Dash: Open Dashboard
Dash->>Query: useQuery(["dashboard"], getDashboardInfo)
Query->>API: GET /admin/dashboard
API-->>Query: 200 OK { userCounts, refundAbortedCounts, applicationCounts }
Query-->>Dash: data
Dash-->>Admin: Render stats with counts
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. 📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (4)
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
📝 추가 및 변경된 파일총 16개 파일 변경 |
✅ Mosu Admin 빌드가 성공적으로 완료되었습니다!📋 빌드 결과
🚀 다음 단계코드 리뷰 후 머지하면 프로덕션에 배포됩니다. |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (27)
mosu-admin/src/hooks/common/useSearchByName.tsx (1)
7-16: Trim input and stabilize handlers with useCallbackTo avoid sending trailing/leading spaces to the API and to keep handler identities stable when passing them down, trim on apply and wrap the handlers with useCallback. This also makes hasNameSearch reflect meaningful text.
Apply:
-import { useState } from "react"; +import { useCallback, useState } from "react"; export const useSearchByName = () => { const [searchName, setSearchName] = useState(""); const [appliedSearchName, setAppliedSearchName] = useState(""); - const handleNameSearch = () => { - setAppliedSearchName(searchName); - }; + const handleNameSearch = useCallback(() => { + setAppliedSearchName(searchName.trim()); + }, [searchName]); - const handleNameReset = () => { - setSearchName(""); - setAppliedSearchName(""); - }; + const handleNameReset = useCallback(() => { + setSearchName(""); + setAppliedSearchName(""); + }, []); - const hasNameSearch = !!appliedSearchName; + const hasNameSearch = appliedSearchName.trim().length > 0;Also applies to: 1-1
mosu-admin/src/hooks/common/useSearchByPhone.tsx (1)
7-16: Normalize phone input and stabilize handlersPhone searches commonly require digit-only values. Normalize by stripping non-digits on apply, trim input, and memoize handlers with useCallback to avoid unnecessary re-renders.
Apply:
-import { useState } from "react"; +import { useCallback, useState } from "react"; export const useSearchByPhone = () => { const [searchPhone, setSearchPhone] = useState(""); const [appliedSearchPhone, setAppliedSearchPhone] = useState(""); - const handlePhoneSearch = () => { - setAppliedSearchPhone(searchPhone); - }; + const handlePhoneSearch = useCallback(() => { + const normalized = searchPhone.trim().replace(/\D/g, ""); + setAppliedSearchPhone(normalized); + }, [searchPhone]); - const handlePhoneReset = () => { - setSearchPhone(""); - setAppliedSearchPhone(""); - }; + const handlePhoneReset = useCallback(() => { + setSearchPhone(""); + setAppliedSearchPhone(""); + }, []); - const hasPhoneSearch = !!appliedSearchPhone; + const hasPhoneSearch = appliedSearchPhone.length > 0;Also applies to: 1-1
mosu-admin/src/hooks/common/usePagination.tsx (2)
8-41: Guard against out-of-range currentPage and use a clamped valueIf upstream passes 0 or a value > totalPages (e.g., after filter changes), visiblePages and next/prev flags can be inconsistent. Clamp currentPage to [1, totalPages] once and derive everything from it.
Apply:
export const usePagination = ({ currentPage, totalPages }: UsePaginationProps) => { - const visiblePages = useMemo(() => { + const safeCurrent = Math.min(Math.max(currentPage, 1), Math.max(totalPages, 1)); + const visiblePages = useMemo(() => { if (totalPages <= 1) return []; - const delta = 2; + const delta = 2; const range = []; const rangeWithDots: (number | string)[] = []; - for ( - let i = Math.max(2, currentPage - delta); - i <= Math.min(totalPages - 1, currentPage + delta); + for ( + let i = Math.max(2, safeCurrent - delta); + i <= Math.min(totalPages - 1, safeCurrent + delta); i++ ) { range.push(i); } - if (currentPage - delta > 2) { + if (safeCurrent - delta > 2) { rangeWithDots.push(1, "..."); } else { rangeWithDots.push(1); } rangeWithDots.push(...range); - if (currentPage + delta < totalPages - 1) { + if (safeCurrent + delta < totalPages - 1) { rangeWithDots.push("...", totalPages); } else { if (totalPages > 1) { rangeWithDots.push(totalPages); } } return rangeWithDots; - }, [currentPage, totalPages]); + }, [safeCurrent, totalPages]); - const canGoPrevious = currentPage > 1; - const canGoNext = currentPage < totalPages; + const canGoPrevious = safeCurrent > 1; + const canGoNext = safeCurrent < totalPages; return { visiblePages, canGoPrevious, canGoNext, }; };
3-6: Optional: make delta configurableIf you anticipate different pagination densities across pages/components, accept an optional delta in props (default 2).
Alternative:
-interface UsePaginationProps { - currentPage: number; - totalPages: number; -} +interface UsePaginationProps { + currentPage: number; + totalPages: number; + delta?: number; +} -export const usePagination = ({ currentPage, totalPages }: UsePaginationProps) => { +export const usePagination = ({ currentPage, totalPages, delta = 2 }: UsePaginationProps) => { - const delta = 2;Also applies to: 12-15
mosu-admin/src/routes.tsx (2)
11-11: Import is fine — consider lazy-loading StudentsPageStudentsPage is a secondary route. Lazy-loading can cut initial bundle size.
Apply minimally:
-import { StudentsPage } from "@/pages/StudentsPage"; +import { Suspense, lazy } from "react"; +const StudentsPage = lazy(() => import("@/pages/StudentsPage"));And wrap the element in Suspense where used (see next comment).
20-20: Route wiring LGTM; optionally wrap with Suspense for lazy importIf you adopt lazy-loading, wrap the element with Suspense.
Suggested route element:
-<Route path={ROUTES.STUDENTS} element={<StudentsPage />} /> +<Route + path={ROUTES.STUDENTS} + element={ + <Suspense fallback={null}> + <StudentsPage /> + </Suspense> + } />mosu-admin/src/hooks/common/index.ts (1)
1-5: Barrel export LGTM; also re-export SortOrder type for ergonomicsThis avoids deep imports for types when consuming the hook.
export { usePagination } from "./usePagination"; export { useSearchByName } from "./useSearchByName"; export { useSearchByPhone } from "./useSearchByPhone"; export { useSortOrder } from "./useSortOrder"; export { useOverlay } from "./useOverlay"; +export type { SortOrder } from "./useSortOrder";mosu-admin/src/components/CommonPagination.tsx (3)
4-4: Prefer importing hooks from the common barrel for consistencyKeeps import style uniform across the codebase and eases refactors.
-import { usePagination } from "@/hooks/common/usePagination"; +import { usePagination } from "@/hooks/common";
26-33: Add accessible labels to navigation arrowsImproves screen-reader usability without changing behavior.
<Button variant="outline" size="sm" onClick={() => onPageChange(currentPage - 1)} - disabled={!canGoPrevious} + disabled={!canGoPrevious} + aria-label="이전 페이지" > <ChevronLeft className="h-4 w-4" /> </Button> @@ <Button variant="outline" size="sm" onClick={() => onPageChange(currentPage + 1)} - disabled={!canGoNext} + disabled={!canGoNext} + aria-label="다음 페이지" > <ChevronRight className="h-4 w-4" /> </Button>Also applies to: 51-56
35-49: Stabilize keys and improve a11y on page items
- Stable keys reduce re-renders when the window shifts.
- aria-current communicates the current page to assistive tech.
- Optional: avoid calling onPageChange via boolean short-circuit.
- {visiblePages.map((page, index) => ( + {visiblePages.map((page, index) => ( <Button - key={index} + key={typeof page === "number" ? `page-${page}` : `ellipsis-${index}`} variant={page === currentPage ? "default" : "outline"} size="sm" - onClick={() => typeof page === "number" && onPageChange(page)} + onClick={typeof page === "number" ? () => onPageChange(page) : undefined} disabled={page === "..."} className={cn( "min-w-8", page === "..." && "cursor-default hover:bg-transparent", )} + aria-current={page === currentPage ? "page" : undefined} + aria-label={typeof page === "number" ? `페이지 ${page}` : "더 보기"} > {page} </Button> ))}mosu-admin/src/hooks/common/useSortOrder.tsx (2)
8-10: Narrow input type and guard at runtime to prevent invalid sort valuesAccepting a plain string risks leaking invalid values downstream. Constrain to SortOrder and add a runtime guard (useful when values come from UI components).
- const handleSortChange = (value: string) => { - setSortOrder(value as SortOrder); - }; + const handleSortChange = (value: unknown) => { + if (value === "asc" || value === "desc") { + setSortOrder(value); + } + };
1-1: Memoize handlers to reduce re-renders in child componentsSmall optimization; keeps handler identity stable when passed to controlled inputs.
-import { useState } from "react"; +import { useCallback, useState } from "react"; @@ - const handleSortReset = () => { - setSortOrder(undefined); - }; + const handleSortReset = useCallback(() => { + setSortOrder(undefined); + }, []);Also applies to: 12-14
mosu-admin/src/components/dashboard/StatSection.tsx (2)
15-15: Expose loading/error state from the query (optional UI polish)Using isLoading/isError enables showing a skeleton or fallback rather than zeroed counts.
- const { data: dashboardCounts } = useGetDashboardInfo(); + const { data: dashboardCounts, isLoading, isError } = useGetDashboardInfo();You can pass isLoading down to StatCard or render a lightweight skeleton locally until data arrives.
62-62: Avoid hard-coded “기준” date on the chart subtitleIf the chart remains static for now, prefer a dynamic “as of” date to prevent stale UI text.
- subtitle="25.06.18 기준" + subtitle={`${new Date().toLocaleDateString("ko-KR")} 기준`}mosu-admin/src/api/dashboard/getDashboardInfo.ts (3)
10-10: Add explicit return type to the API functionBeing explicit helps with inference across call sites and prevents accidental return-shape drift.
Apply:
-export const getDashboardInfo = async () => { +export const getDashboardInfo = async (): Promise<GetDashboardInfoResponseBody> => {
15-18: Preserve error context when rethrowingCatching and throwing a new blank
Error()drops the original message/stack. Just rethrow or wrap with a message (optionally usingcauseif your TS lib target supports it).Apply one of:
- } catch (error) { - console.error(error); - throw new Error(); - } + } catch (error) { + if (process.env.NODE_ENV !== "production") console.error(error); + throw error as Error; + }or
- } catch (error) { - console.error(error); - throw new Error(); - } + } catch (error) { + throw new Error("Failed to fetch dashboard info", { cause: error as Error }); + }
21-25: Consider query options tuned for dashboards (freshness/refresh)Dashboards usually benefit from periodic refresh and less jitter. Optionally set
staleTimeand a lightrefetchInterval.Example:
export const useGetDashboardInfo = () => { return useQuery({ queryKey: ["dashboard"], queryFn: getDashboardInfo, + staleTime: 30_000, + refetchOnWindowFocus: false, + refetchInterval: 30_000, }); };mosu-admin/src/pages/StudentsPage.tsx (6)
6-6: Memoize filter object to stabilize queryKey and avoid unnecessary churn
getSearchFilter()creates a new object each render. React Query hashes keys, so this is mostly fine, but memoizing makes identity stable and reduces noise.Apply:
-import { useState } from "react"; +import { useMemo, useState } from "react";- const getSearchFilter = () => ({ - ...(appliedSearchName && { name: appliedSearchName }), - ...(appliedSearchPhone && { phone: appliedSearchPhone }), - ...(sortOrder && { order: sortOrder }), - }); + const searchFilter = useMemo( + () => ({ + ...(appliedSearchName && { name: appliedSearchName }), + ...(appliedSearchPhone && { phone: appliedSearchPhone }), + ...(sortOrder && { order: sortOrder }), + }), + [appliedSearchName, appliedSearchPhone, sortOrder], + );- } = useGetStudentList({ - filter: getSearchFilter(), + } = useGetStudentList({ + filter: searchFilter, pageable: { page: currentPage - 1, size: 30, }, });
62-65: Narrow the sort type to enforce "asc" | "desc" at the boundaryThe handler currently accepts
string. Narrowing here prevents unexpected values flowing into the API.Apply:
- const handleSortChangeWithPageReset = (value: string) => { + const handleSortChangeWithPageReset = (value: "asc" | "desc") => { handleSortChange(value); setCurrentPage(1); };- <Select - value={sortOrder} - onValueChange={handleSortChangeWithPageReset} - > + <Select + value={sortOrder} + onValueChange={(v) => handleSortChangeWithPageReset(v as "asc" | "desc")} + >Also applies to: 113-118
67-71: Tighten keyboard event typing for inputsAdd the element generic to improve type safety.
Apply:
- const handleKeyPress = (e: React.KeyboardEvent) => { + const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => { if (e.key === "Enter") { handleSearch(); } };
159-189: Empty-state row and stable keys for table items
- Show an empty row when
content.length === 0for clearer UX.- Avoid
indexas key; prefer a stable composite (e.g., phoneNumber + birthDate) to reduce reconciliation glitches when paginating.Apply:
- <TableBody> - {studentData?.content?.map((student, index) => ( - <TableRow key={index}> + <TableBody> + {studentData.content.length === 0 ? ( + <TableRow> + <TableCell colSpan={8} className="py-8 text-center text-gray-500"> + 조회된 학생이 없습니다. + </TableCell> + </TableRow> + ) : ( + studentData.content.map((student) => ( + <TableRow key={`${student.phoneNumber}-${student.birthDate}`}> <TableCell className="border-r"> {student.name} </TableCell> <TableCell className="border-r"> {student.birthDate} </TableCell> <TableCell className="border-r"> {student.phoneNumber} </TableCell> <TableCell className="border-r"> {student.gender} </TableCell> <TableCell className="border-r"> {student.educationLevel} </TableCell> <TableCell className="border-r"> {student.schoolName} </TableCell> <TableCell className="border-r"> {student.grade} </TableCell> <TableCell className="border-r"> {student.examCount} </TableCell> - </TableRow> - ))} + </TableRow> + )) + )} </TableBody>
154-156: Column header vs. field mismatch: verify the domain termHeader text says “시청 신청 수” but the field used is
examCount. If this column represents “신청 횟수” (applications) or “도시락 신청 수”, the backing property name should align (e.g.,applicationCount). If it truly is exam-related, adjust the header.Would you like me to propagate a consistent name across API types and UI?
Also applies to: 183-185
73-76: Optional: add a retry path and avoid full-page “로딩 중...” flash on pagination
- For paginated tables, consider keeping previous data during page transitions (see hook suggestion below) and rendering a lightweight inline spinner instead of replacing the whole page.
- You can also expose
refetchfrom the hook and provide a retry button in the error state.If you want, I can wire a small inline spinner and a Retry button pattern consistent with other pages.
mosu-admin/src/api/dashboard/getStudentList.ts (4)
61-61: Add explicit return typeClarifies the contract and helps callers.
Apply:
-export const getStudentList = async (params?: GetStudentListParams) => { +export const getStudentList = async (params?: GetStudentListParams): Promise<GetStudentListResponseBody> => {
80-83: Preserve error details on failureSame rationale as the dashboard API. Don’t throw a blank
Error().Apply:
- } catch (error) { - console.error(error); - throw new Error(); - } + } catch (error) { + if (process.env.NODE_ENV !== "production") console.error(error); + throw error as Error; + }
29-43: Avoid duplicating pagination/sort meta types across modules
Sort/Pageablehere look similar to those used insrc/api/application/getApplications.ts(wherePageable.sort: SortInfo). Divergent duplicates drift quickly.Consider extracting shared types into
src/api/types/pagination.ts(e.g.,SortInfo,Pageable) and reusing them here and elsewhere. I can open a follow-up PR to consolidate if you’d like.Also applies to: 35-42
86-91: Pagination UX: keep previous data while fetching the next pageReduces flicker when changing pages/sorting.
Apply:
export const useGetStudentList = (params?: GetStudentListParams) => { return useQuery({ queryKey: ["student", params], queryFn: () => getStudentList(params), + // v4/v5-safe way to emulate keepPreviousData + placeholderData: (prev) => prev, + staleTime: 15_000, }); };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (15)
mosu-admin/src/api/dashboard/getDashboardInfo.ts(1 hunks)mosu-admin/src/api/dashboard/getStudentList.ts(1 hunks)mosu-admin/src/components/CommonPagination.tsx(4 hunks)mosu-admin/src/components/dashboard/StatSection.tsx(2 hunks)mosu-admin/src/components/dashboard/StudentTable.tsx(0 hunks)mosu-admin/src/constants/routes.ts(1 hunks)mosu-admin/src/hooks/common/index.ts(1 hunks)mosu-admin/src/hooks/common/usePagination.tsx(1 hunks)mosu-admin/src/hooks/common/useSearchByName.tsx(1 hunks)mosu-admin/src/hooks/common/useSearchByPhone.tsx(1 hunks)mosu-admin/src/hooks/common/useSortOrder.tsx(1 hunks)mosu-admin/src/layout/navItems.tsx(1 hunks)mosu-admin/src/pages/DashboardPage.tsx(0 hunks)mosu-admin/src/pages/StudentsPage.tsx(1 hunks)mosu-admin/src/routes.tsx(2 hunks)
💤 Files with no reviewable changes (2)
- mosu-admin/src/components/dashboard/StudentTable.tsx
- mosu-admin/src/pages/DashboardPage.tsx
🧰 Additional context used
🧬 Code graph analysis (9)
mosu-admin/src/hooks/common/useSearchByName.tsx (1)
mosu-admin/src/hooks/common/index.ts (1)
useSearchByName(2-2)
mosu-admin/src/routes.tsx (2)
mosu-admin/src/constants/routes.ts (1)
ROUTES(1-26)mosu-admin/src/pages/StudentsPage.tsx (1)
StudentsPage(12-201)
mosu-admin/src/hooks/common/useSortOrder.tsx (1)
mosu-admin/src/hooks/common/index.ts (1)
useSortOrder(4-4)
mosu-admin/src/pages/StudentsPage.tsx (3)
mosu-admin/src/api/dashboard/getStudentList.ts (1)
useGetStudentList(86-91)mosu-admin/src/common/ui/PageWrapper.tsx (1)
PageWrapper(8-15)mosu-admin/src/components/CommonPagination.tsx (1)
CommonPagination(12-61)
mosu-admin/src/layout/navItems.tsx (1)
mosu-admin/src/constants/routes.ts (1)
ROUTES(1-26)
mosu-admin/src/hooks/common/usePagination.tsx (1)
mosu-admin/src/hooks/common/index.ts (1)
usePagination(1-1)
mosu-admin/src/api/dashboard/getStudentList.ts (1)
mosu-admin/src/api/application/getApplications.ts (1)
Pageable(42-49)
mosu-admin/src/hooks/common/useSearchByPhone.tsx (1)
mosu-admin/src/hooks/common/index.ts (1)
useSearchByPhone(3-3)
mosu-admin/src/components/dashboard/StatSection.tsx (2)
mosu-admin/src/api/dashboard/getDashboardInfo.ts (1)
useGetDashboardInfo(21-26)mosu-admin/src/components/dashboard/StatCard.tsx (1)
StatItem(4-10)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Run DangerJS
🔇 Additional comments (11)
mosu-admin/src/hooks/common/useSearchByName.tsx (1)
18-26: Hook shape is clear and cohesive — good abstractionThe returned API is small and composable; this pairs well with the StudentsPage usage. No blocking issues.
mosu-admin/src/hooks/common/useSearchByPhone.tsx (2)
18-26: Solid, mirrored API with name-search hookParity with useSearchByName helps keep StudentsPage clean. Nice.
3-9: Confirm phone filter format with backendThe current implementation in
useSearchByPhone.tsxsimply passes the rawsearchPhonevalue through to the API (viaappliedSearchPhone), and ingetStudentList.ts, that value is assigned directly torequestParams.phone. There’s no normalization of formatting (e.g., stripping dashes or spaces), so if a user enters “010-1234-5678” the backend will receive that exact string.Please confirm the backend’s expected phone filter format:
• If the API only accepts digits (e.g., “01012345678”), update
handlePhoneSearchto strip non-digits before applying the search:- const handlePhoneSearch = () => { - setAppliedSearchPhone(searchPhone); - }; + const handlePhoneSearch = () => { + const normalized = searchPhone.replace(/\D/g, ""); + setAppliedSearchPhone(normalized); + };• If the backend supports formatted strings, no change is required—but consider whether you want to preserve the raw user input for display while sending a normalized value under the hood.
mosu-admin/src/hooks/common/usePagination.tsx (1)
46-51: Clean separation of concerns — hook returns exactly what the pagination UI needsReturn shape is minimal and sufficient (pages + prev/next availability). Looks good.
mosu-admin/src/constants/routes.ts (1)
7-7: Route constant addition is consistent and non-breakingSTUDENTS aligns with the new route and nav usage. Good to see this centralized in ROUTES.
mosu-admin/src/layout/navItems.tsx (2)
2-2: Icon import sanity checkAll imported icons are used in this file. No dead imports.
10-14: Approve STUDENTS nav item — route and page wired correctly
- mosu-admin/src/routes.tsx
• Line 11 importsStudentsPage
• Line 20 registers<Route path={ROUTES.STUDENTS} element={<StudentsPage />} />- mosu-admin/src/pages/StudentsPage.tsx exports the
StudentsPagecomponent- mosu-admin/src/layout/navItems.tsx includes the STUDENTS nav item under the dashboard
All wiring is in place, so this nav link will not be dead.
mosu-admin/src/components/CommonPagination.tsx (1)
17-20: Hook integration LGTMThe switch to usePagination simplifies the component and centralizes page-window logic.
mosu-admin/src/components/dashboard/StatSection.tsx (1)
26-45: Counts wiring LGTM; fallback to 0 is safeThe optional chaining with numeric fallback prevents NaN/undefined leaks into UI. Consider formatting with toLocaleString for readability, but current implementation is acceptable.
mosu-admin/src/api/dashboard/getStudentList.ts (2)
65-73: Param serialization: ensure consistent sort semanticsYou pass
filter.orderseparately frompageable.sort. If the backend expectssort=field,orderrather than a separateorderparam, requests may not sort as intended. Current UI only setsorderwithout afield.Do we have a default sort field on the backend (e.g., createdAt)? If not, we should either:
- include a sort field (e.g.,
sort: ["createdAt," + order]), or- change the API to accept
orderas you modeled.
I can help align client and server if you share the backend contract.
75-79: Ambient BaseResponse Declaration – No Import RequiredThe
BaseResponse<T>type is declared as an ambient type inmosu-admin/src/augmentations/api.d.ts, making it globally available throughout the admin codebase. Therefore, no explicit import ofBaseResponseis needed ingetStudentList.ts.Likely an incorrect or invalid review comment.
| const { data: response } = | ||
| await api.get<BaseResponse<GetDashboardInfoResponseBody>>("/admin/dashboard"); | ||
| return response.data; |
There was a problem hiding this comment.
💡 Verification agent
❓ Verification inconclusive
BaseResponse generic is referenced but not imported
BaseResponse<GetDashboardInfoResponseBody> is used in the axios call, but there is no import for BaseResponse. If it isn’t declared as a global ambient type, this will fail type-checking.
Run this to locate the declaration and confirm the intended import path:
🏁 Script executed:
#!/bin/bash
# Find where BaseResponse is defined and how it's exported
rg -nP '^(export\s+)?(type|interface)\s+BaseResponse\b' -C2
# Find existing imports of BaseResponse to copy the correct path
rg -nP 'import\s+type\s+\{\s*BaseResponse\s*\}' -nLength of output: 114
Add missing import for BaseResponse
It appears there is no declaration or import of the BaseResponse type anywhere in the codebase, so this will break TypeScript compilation. You need to import BaseResponse from the module where it’s defined (or add a declaration if it doesn’t yet exist).
Please update mosu-admin/src/api/dashboard/getDashboardInfo.ts to include the correct import. For example:
+ import type { BaseResponse } from "../types"; // ← adjust the path to where BaseResponse is actually exported📝 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.
| const { data: response } = | |
| await api.get<BaseResponse<GetDashboardInfoResponseBody>>("/admin/dashboard"); | |
| return response.data; | |
| import type { BaseResponse } from "../types"; // ← adjust the path to where BaseResponse is actually exported | |
| const { data: response } = | |
| await api.get<BaseResponse<GetDashboardInfoResponseBody>>("/admin/dashboard"); | |
| return response.data; |
🤖 Prompt for AI Agents
In mosu-admin/src/api/dashboard/getDashboardInfo.ts around lines 12 to 14, the
code uses the BaseResponse generic but does not import or declare it; add an
import for BaseResponse from the module that defines it (e.g. import {
BaseResponse } from '<path-to-shared-types>') at the top of the file, or if the
type doesn't exist yet, create and export a BaseResponse type in the
shared/types module and then import it here so the TypeScript compilation
succeeds.
| const statsData: StatItem[] = [ | ||
| { | ||
| id: "total-applicants", | ||
| title: "없는 필드입니다", | ||
| value: 0, | ||
| unit: "명", | ||
| icon: <Users2 size={24} />, | ||
| }, |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Remove placeholder stat (“없는 필드입니다”) before merging
Shipping a hard-coded placeholder can confuse admins and undermine trust in metrics.
- {
- id: "total-applicants",
- title: "없는 필드입니다",
- value: 0,
- unit: "명",
- icon: <Users2 size={24} />,
- },📝 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.
| const statsData: StatItem[] = [ | |
| { | |
| id: "total-applicants", | |
| title: "없는 필드입니다", | |
| value: 0, | |
| unit: "명", | |
| icon: <Users2 size={24} />, | |
| }, | |
| const statsData: StatItem[] = [ | |
| ]; |
✅ Mosu Admin 빌드가 성공적으로 완료되었습니다!📋 빌드 결과
🚀 다음 단계코드 리뷰 후 머지하면 프로덕션에 배포됩니다. |
✅ Linked Issue
🔍 What I did
🚧 TODO (if any)
Summary by CodeRabbit
New Features
Improvements
Changes
Chores