diff --git a/app/(dashboard)/_ui/list-header/index.tsx b/app/(dashboard)/_ui/list-header/index.tsx index c1edf917..b8ace106 100644 --- a/app/(dashboard)/_ui/list-header/index.tsx +++ b/app/(dashboard)/_ui/list-header/index.tsx @@ -4,12 +4,19 @@ import styles from './styles.module.scss' const cx = classNames.bind(styles) -const LIST_HEADER = ['전략', '분석', 'MDD', 'SM SCORE', '수익률', '구독'] +const LIST_HEADER = { + default: ['전략', '분석', 'MDD', 'SM SCORE', '수익률', '구독'], + my: ['전략', '분석', 'MDD', 'SM SCORE', '수익률', '공개', '관리'], +} + +interface Props { + type?: 'default' | 'my' +} -const ListHeader = () => { +const ListHeader = ({ type = 'default' }: Props) => { return ( -
- {LIST_HEADER.map((category) => ( +
+ {LIST_HEADER[type].map((category) => (
{category}
diff --git a/app/(dashboard)/_ui/list-header/styles.module.scss b/app/(dashboard)/_ui/list-header/styles.module.scss index 0050d4c6..ee5053ef 100644 --- a/app/(dashboard)/_ui/list-header/styles.module.scss +++ b/app/(dashboard)/_ui/list-header/styles.module.scss @@ -5,6 +5,10 @@ height: 42px; margin: 20px 0 10px; + &.my { + grid-template-columns: 3fr 1.5fr 1.7fr 1.3fr 1.5fr 1.2fr 1.2fr; + } + .category { display: flex; align-items: center; diff --git a/app/(dashboard)/my/_api/get-my-strategy-list.ts b/app/(dashboard)/my/_api/get-my-strategy-list.ts new file mode 100644 index 00000000..3c072bcc --- /dev/null +++ b/app/(dashboard)/my/_api/get-my-strategy-list.ts @@ -0,0 +1,19 @@ +import axiosInstance from '@/shared/api/axios' +import { StrategiesModel } from '@/shared/types/strategy-details-data' + +// 실제 api 나오면 수정 필요함 +// totalElements 사용해서 hasmore값 계산해야될 것 같음 + +interface StrategiesResponseModel { + result: { + strategies: StrategiesModel[] + hasMore: boolean + } +} + +export const getMyStrategyList = async ({ page = 1, size = 4 }: { page: number; size: number }) => { + const response = await axiosInstance.get( + `/api/my-strategies/page=${page}&size=${size}` + ) + return response.data.result +} diff --git a/app/(dashboard)/my/_hooks/query/use-get-my-strategy-list.ts b/app/(dashboard)/my/_hooks/query/use-get-my-strategy-list.ts new file mode 100644 index 00000000..8b87cc7c --- /dev/null +++ b/app/(dashboard)/my/_hooks/query/use-get-my-strategy-list.ts @@ -0,0 +1,24 @@ +import { getMyStrategyList } from '@/app/(dashboard)/my/_api/get-my-strategy-list' +import { useInfiniteQuery } from '@tanstack/react-query' + +import { StrategiesModel } from '@/shared/types/strategy-details-data' + +interface StrategiesPageModel { + strategies: StrategiesModel[] + hasMore: boolean +} + +export const useGetMyStrategyList = () => { + return useInfiniteQuery({ + queryKey: ['myStrategies'], + queryFn: async ({ pageParam = 1 }) => { + const page = typeof pageParam === 'number' ? pageParam : 1 + return getMyStrategyList({ page, size: 4 }) + }, + getNextPageParam: (lastPage, pages) => { + if (!lastPage.hasMore) return undefined + return pages.length + 1 + }, + initialPageParam: 1, + }) +} diff --git a/app/(dashboard)/my/strategies/_ui/my-strategy-list/index.tsx b/app/(dashboard)/my/strategies/_ui/my-strategy-list/index.tsx new file mode 100644 index 00000000..29e5edc1 --- /dev/null +++ b/app/(dashboard)/my/strategies/_ui/my-strategy-list/index.tsx @@ -0,0 +1,42 @@ +'use client' + +import { useCallback, useRef } from 'react' + +import StrategiesItem from '@/app/(dashboard)/_ui/strategies-item' +import { useGetMyStrategyList } from '@/app/(dashboard)/my/_hooks/query/use-get-my-strategy-list' + +import { useIntersectionObserver } from '@/shared/hooks/custom/use-intersection-observer' + +const MyStrategyList = () => { + const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useGetMyStrategyList() + + const loadMoreRef = useRef(null) + + const onIntersect = useCallback( + (entry: IntersectionObserverEntry) => { + if (entry.isIntersecting && hasNextPage && !isFetchingNextPage) { + fetchNextPage() + } + }, + [fetchNextPage, hasNextPage, isFetchingNextPage] + ) + + useIntersectionObserver({ + ref: loadMoreRef, + onIntersect, + }) + + const strategies = data?.pages.flatMap((page) => page.strategies) || [] + + return ( + <> + {strategies.map((strategy) => ( + + ))} +
+ {isFetchingNextPage &&
로딩 중...
} + + ) +} + +export default MyStrategyList diff --git a/app/(dashboard)/my/strategies/page.tsx b/app/(dashboard)/my/strategies/page.tsx index 6f7e77b5..7010a8a5 100644 --- a/app/(dashboard)/my/strategies/page.tsx +++ b/app/(dashboard)/my/strategies/page.tsx @@ -1,5 +1,25 @@ +import { Suspense } from 'react' + +import classNames from 'classnames/bind' + +import Title from '@/shared/ui/title' + +import ListHeader from '../../_ui/list-header' +import MyStrategyList from './_ui/my-strategy-list' +import styles from './styles.module.scss' + +const cx = classNames.bind(styles) + const MyStrategiesPage = () => { - return <> + return ( +
+ + <ListHeader type="my" /> + <Suspense fallback={<div>Loading...</div>}> + <MyStrategyList /> + </Suspense> + </div> + ) } export default MyStrategiesPage diff --git a/app/(dashboard)/my/strategies/styles.module.scss b/app/(dashboard)/my/strategies/styles.module.scss new file mode 100644 index 00000000..f481a8ff --- /dev/null +++ b/app/(dashboard)/my/strategies/styles.module.scss @@ -0,0 +1,3 @@ +.container { + margin-top: 80px; +} diff --git a/shared/hooks/custom/use-intersection-observer.ts b/shared/hooks/custom/use-intersection-observer.ts new file mode 100644 index 00000000..0ffbf5bf --- /dev/null +++ b/shared/hooks/custom/use-intersection-observer.ts @@ -0,0 +1,37 @@ +import { RefObject, useEffect } from 'react' + +interface UseIntersectionObserverProps { + ref: RefObject<HTMLElement> + onIntersect: (entry: IntersectionObserverEntry) => void + threshold?: number + rootMargin?: string +} + +export const useIntersectionObserver = ({ + ref, + onIntersect, + threshold = 0.1, + rootMargin = '0px', +}: UseIntersectionObserverProps) => { + useEffect(() => { + if (!ref.current) return + + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => onIntersect(entry)) + }, + { + threshold, + rootMargin, + } + ) + + observer.observe(ref.current) + + return () => { + if (ref.current) { + observer.unobserve(ref.current) + } + } + }, [ref, threshold, rootMargin, onIntersect]) +} diff --git a/shared/types/auth.ts b/shared/types/auth.ts index 1025dc33..6b24d3cf 100644 --- a/shared/types/auth.ts +++ b/shared/types/auth.ts @@ -53,10 +53,10 @@ export interface TokenStatusModel { export const isAdmin = (user: UserModel | null): boolean => { if (!user) return false - return user.role.includes('admin') + return user.role.includes('ADMIN') } export const isTrader = (user: UserModel | null): boolean => { if (!user) return false - return user.role.includes('trader') + return user.role.includes('TRADER') }