diff --git a/app/(admin)/admin/detail/[id]/sincerity-temp/page.tsx b/app/(admin)/admin/detail/[id]/sincerity-temp/page.tsx index ea0e590f..69f6bffb 100644 --- a/app/(admin)/admin/detail/[id]/sincerity-temp/page.tsx +++ b/app/(admin)/admin/detail/[id]/sincerity-temp/page.tsx @@ -1,3 +1,71 @@ -export default function SincerityTempPage() { - return
SincerityTempPage
; +import { + dehydrate, + HydrationBoundary, + QueryClient, +} from '@tanstack/react-query'; +import { getSincerityTemperatureHistoryInServer } from '@/features/admin/api/sincerity-temperature-history.server'; +import { GetSincerityTemperatureHistoryResponse } from '@/features/admin/api/types'; +import SincerityTempTable from '@/features/admin/ui/sincerity-temp-table'; +import { getSincerityPresetByLevelName } from '@/shared/config/sincerity-temp-presets'; + +const LEVEL_NAME_MAP = { + FIRST: '1단계', + SECOND: '2단계', + THIRD: '3단계', + FOURTH: '4단계', +}; + +export default async function SincerityTempPage({ + params, +}: { + params: Promise<{ id: string }>; +}) { + const queryClient = new QueryClient(); + const { id } = await params; + + const memberId = Number(id); + + // 서버 side에서 첫 페이지 데이터 미리 가져오기 + await queryClient.prefetchQuery({ + queryKey: ['sincerityTemperatureHistory', memberId, 1], // "sincerityTemperatureHistory", memberId, page + queryFn: () => + getSincerityTemperatureHistoryInServer({ + memberId, + page: 1, + }), + }); + + const data: GetSincerityTemperatureHistoryResponse = + await queryClient.getQueryData([ + 'sincerityTemperatureHistory', + memberId, + 1, + ]); + + const temperPreset = getSincerityPresetByLevelName( + LEVEL_NAME_MAP[data.sincerityTempLevel], + ); + + return ( + +
+
+ + 현재 성실 온도 + + +
+ + + {data.currentSincerityTemperature} ℃ + +
+
+ + +
+
+ ); } diff --git a/public/icons/trending-down.svg b/public/icons/trending-down.svg new file mode 100644 index 00000000..7a615a60 --- /dev/null +++ b/public/icons/trending-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/trending-up.svg b/public/icons/trending-up.svg new file mode 100644 index 00000000..1ea42c7d --- /dev/null +++ b/public/icons/trending-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/features/admin/api/sincerity-temperature-history.server.ts b/src/features/admin/api/sincerity-temperature-history.server.ts new file mode 100644 index 00000000..e8ff25be --- /dev/null +++ b/src/features/admin/api/sincerity-temperature-history.server.ts @@ -0,0 +1,19 @@ +import { axiosServerInstance } from '@/shared/tanstack-query/axios.server'; +import { + GetSincerityTemperatureHistoryRequest, + GetSincerityTemperatureHistoryResponse, +} from './types'; + +// 성실 온도 이력 조회 API 요청 함수 +export const getSincerityTemperatureHistoryInServer = async ({ + memberId, + page = 1, +}: GetSincerityTemperatureHistoryRequest): Promise => { + const queryString = `page=${page}&page-size=10`; + + const res = await axiosServerInstance.get( + `/admin/members/${memberId}/sincerity-temperature-histories?${queryString}`, + ); + + return res.data.content; +}; diff --git a/src/features/admin/api/sincerity-temperature-history.ts b/src/features/admin/api/sincerity-temperature-history.ts new file mode 100644 index 00000000..188f202f --- /dev/null +++ b/src/features/admin/api/sincerity-temperature-history.ts @@ -0,0 +1,19 @@ +import { axiosInstance } from '@/shared/tanstack-query/axios'; +import { + GetSincerityTemperatureHistoryRequest, + GetSincerityTemperatureHistoryResponse, +} from './types'; + +// 성실 온도 이력 조회 API 요청 함수 +export const getSincerityTemperatureHistory = async ({ + memberId, + page = 1, +}: GetSincerityTemperatureHistoryRequest): Promise => { + const queryString = `page=${page}&page-size=10`; + + const res = await axiosInstance.get( + `/admin/members/${memberId}/sincerity-temperature-histories?${queryString}`, + ); + + return res.data.content; +}; diff --git a/src/features/admin/api/types.ts b/src/features/admin/api/types.ts index f85d2cb9..494cde1b 100644 --- a/src/features/admin/api/types.ts +++ b/src/features/admin/api/types.ts @@ -2,6 +2,7 @@ export type RoleId = 'ROLE_MEMBER' | 'ROLE_ADMIN' | 'ROLE_MENTOR'; type RoleName = '일반' | '관리자' | '멘토'; export type MemberStatus = 'ACTIVE' | 'PAUSED' | 'PERM_BAN' | 'DORMANT'; +// 사용자 리스트 조회 API 요청 타입 export interface GetMemberListRequest { roleId?: RoleId; memberStatus?: MemberStatus; @@ -9,6 +10,7 @@ export interface GetMemberListRequest { page?: number; } +// 사용자 리스트 조회 API 응답 타입 export interface GetMemberListResponse { page: number; size: number; @@ -29,10 +31,12 @@ export interface GetMemberListResponse { }[]; } +// 사용자 계정 이력 조회 API 요청 타입 export interface GetAccountHistoriesRequest { memberId: number; } +// 사용자 계정 이력 조회 API 응답 타입 export interface GetAccountHistoriesResponse { memberId: number; joinedAt: string; @@ -50,12 +54,39 @@ export interface GetAccountHistoriesResponse { }[]; } +// 사용자 상태 변경 API 요청 타입 export interface ChangeMemberStatusRequest { memberId: number; to: MemberStatus; } +// 사용자 권한 변경 API 요청 타입 export interface ChangeMemberRoleRequest { memberId: number; roleId: RoleId; } + +// 성실 온도 이력 조회 API 요청 타입 +export interface GetSincerityTemperatureHistoryRequest { + memberId: number; + page: number; +} + +// 성실 온도 이력 조회 API 응답 타입 +export interface GetSincerityTemperatureHistoryResponse { + currentSincerityTemperature: number; + sincerityTempLevel: 'FIRST' | 'SECOND' | 'THIRD' | 'FOURTH'; + sincerityTemperatureHistory: { + content: { + reasonType: 'STUDY_REVIEW'; + increment: number; + recordedAt: string; + }[]; + page: number; + size: number; + totalElements: number; + totalPages: number; + hasNext: boolean; + hasPrevious: boolean; + }; +} diff --git a/src/features/admin/model/use-sincerity-temperature-history-query.ts b/src/features/admin/model/use-sincerity-temperature-history-query.ts new file mode 100644 index 00000000..a183962c --- /dev/null +++ b/src/features/admin/model/use-sincerity-temperature-history-query.ts @@ -0,0 +1,17 @@ +import { useQuery } from '@tanstack/react-query'; +import { getSincerityTemperatureHistory } from '../api/sincerity-temperature-history'; +import { GetSincerityTemperatureHistoryRequest } from '../api/types'; + +export const useGetSincerityTemperatureHistoryQuery = ({ + memberId, + page, +}: GetSincerityTemperatureHistoryRequest) => { + return useQuery({ + queryKey: ['sincerityTemperatureHistory', memberId, page], + queryFn: () => + getSincerityTemperatureHistory({ + memberId, + page, + }), + }); +}; diff --git a/src/features/admin/ui/sincerity-temp-table.tsx b/src/features/admin/ui/sincerity-temp-table.tsx new file mode 100644 index 00000000..5490dca3 --- /dev/null +++ b/src/features/admin/ui/sincerity-temp-table.tsx @@ -0,0 +1,82 @@ +'use client'; + +import { useState } from 'react'; +import { formatYYYYMMDD } from '@/shared/lib/time'; +import Pagination from '@/shared/ui/pagination'; +import TrendingDown from 'public/icons/trending-down.svg'; +import TrendingUp from 'public/icons/trending-up.svg'; +import { useGetSincerityTemperatureHistoryQuery } from '../model/use-sincerity-temperature-history-query'; + +interface SincerityTempTableProps { + memberId: number; +} + +export default function SincerityTempTable({ + memberId, +}: SincerityTempTableProps) { + const [page, setPage] = useState(1); + + const { data } = useGetSincerityTemperatureHistoryQuery({ + memberId, + page, + }); + + const sincerityTemperatureHistory = data?.sincerityTemperatureHistory.content; + + return ( + <> +
+ + 성실온도 내역 + + +
+ {sincerityTemperatureHistory?.length > 0 ? ( + sincerityTemperatureHistory.map((history, idx) => ( +
+
+ + {history.reasonType === 'STUDY_REVIEW' ? '스터디 리뷰' : ''} + + + {formatYYYYMMDD(history.recordedAt)} + +
+ +
+ {history.increment > 0 ? ( + <> + + +{history.increment}℃ + + + + ) : ( + <> + + {history.increment}℃ + + + + )} +
+
+ )) + ) : ( +
+ 성실 온도 내역이 없습니다. +
+ )} +
+
+ + + + ); +}