Skip to content

Commit dea58f8

Browse files
author
Yang_Hae_Seok
authored
Merge pull request #142 from FC-DEV-FinalProject/feat/my-page-strategies-125
[Feat] 나의 전략 컴포넌트 추가
2 parents 9e975a3 + a024de1 commit dea58f8

9 files changed

Lines changed: 163 additions & 7 deletions

File tree

app/(dashboard)/_ui/list-header/index.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,19 @@ import styles from './styles.module.scss'
44

55
const cx = classNames.bind(styles)
66

7-
const LIST_HEADER = ['전략', '분석', 'MDD', 'SM SCORE', '수익률', '구독']
7+
const LIST_HEADER = {
8+
default: ['전략', '분석', 'MDD', 'SM SCORE', '수익률', '구독'],
9+
my: ['전략', '분석', 'MDD', 'SM SCORE', '수익률', '공개', '관리'],
10+
}
11+
12+
interface Props {
13+
type?: 'default' | 'my'
14+
}
815

9-
const ListHeader = () => {
16+
const ListHeader = ({ type = 'default' }: Props) => {
1017
return (
11-
<div className={cx('container')}>
12-
{LIST_HEADER.map((category) => (
18+
<div className={cx('container', type)}>
19+
{LIST_HEADER[type].map((category) => (
1320
<div key={category} className={cx('category')}>
1421
{category}
1522
</div>

app/(dashboard)/_ui/list-header/styles.module.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
height: 42px;
66
margin: 20px 0 10px;
77

8+
&.my {
9+
grid-template-columns: 3fr 1.5fr 1.7fr 1.3fr 1.5fr 1.2fr 1.2fr;
10+
}
11+
812
.category {
913
display: flex;
1014
align-items: center;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import axiosInstance from '@/shared/api/axios'
2+
import { StrategiesModel } from '@/shared/types/strategy-details-data'
3+
4+
// 실제 api 나오면 수정 필요함
5+
// totalElements 사용해서 hasmore값 계산해야될 것 같음
6+
7+
interface StrategiesResponseModel {
8+
result: {
9+
strategies: StrategiesModel[]
10+
hasMore: boolean
11+
}
12+
}
13+
14+
export const getMyStrategyList = async ({ page = 1, size = 4 }: { page: number; size: number }) => {
15+
const response = await axiosInstance.get<StrategiesResponseModel>(
16+
`/api/my-strategies/page=${page}&size=${size}`
17+
)
18+
return response.data.result
19+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { getMyStrategyList } from '@/app/(dashboard)/my/_api/get-my-strategy-list'
2+
import { useInfiniteQuery } from '@tanstack/react-query'
3+
4+
import { StrategiesModel } from '@/shared/types/strategy-details-data'
5+
6+
interface StrategiesPageModel {
7+
strategies: StrategiesModel[]
8+
hasMore: boolean
9+
}
10+
11+
export const useGetMyStrategyList = () => {
12+
return useInfiniteQuery<StrategiesPageModel, Error>({
13+
queryKey: ['myStrategies'],
14+
queryFn: async ({ pageParam = 1 }) => {
15+
const page = typeof pageParam === 'number' ? pageParam : 1
16+
return getMyStrategyList({ page, size: 4 })
17+
},
18+
getNextPageParam: (lastPage, pages) => {
19+
if (!lastPage.hasMore) return undefined
20+
return pages.length + 1
21+
},
22+
initialPageParam: 1,
23+
})
24+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
'use client'
2+
3+
import { useCallback, useRef } from 'react'
4+
5+
import StrategiesItem from '@/app/(dashboard)/_ui/strategies-item'
6+
import { useGetMyStrategyList } from '@/app/(dashboard)/my/_hooks/query/use-get-my-strategy-list'
7+
8+
import { useIntersectionObserver } from '@/shared/hooks/custom/use-intersection-observer'
9+
10+
const MyStrategyList = () => {
11+
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useGetMyStrategyList()
12+
13+
const loadMoreRef = useRef<HTMLDivElement>(null)
14+
15+
const onIntersect = useCallback(
16+
(entry: IntersectionObserverEntry) => {
17+
if (entry.isIntersecting && hasNextPage && !isFetchingNextPage) {
18+
fetchNextPage()
19+
}
20+
},
21+
[fetchNextPage, hasNextPage, isFetchingNextPage]
22+
)
23+
24+
useIntersectionObserver({
25+
ref: loadMoreRef,
26+
onIntersect,
27+
})
28+
29+
const strategies = data?.pages.flatMap((page) => page.strategies) || []
30+
31+
return (
32+
<>
33+
{strategies.map((strategy) => (
34+
<StrategiesItem key={strategy.strategyId} strategiesData={strategy} />
35+
))}
36+
<div ref={loadMoreRef} />
37+
{isFetchingNextPage && <div>로딩 중...</div>}
38+
</>
39+
)
40+
}
41+
42+
export default MyStrategyList
Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
1+
import { Suspense } from 'react'
2+
3+
import classNames from 'classnames/bind'
4+
5+
import Title from '@/shared/ui/title'
6+
7+
import ListHeader from '../../_ui/list-header'
8+
import MyStrategyList from './_ui/my-strategy-list'
9+
import styles from './styles.module.scss'
10+
11+
const cx = classNames.bind(styles)
12+
113
const MyStrategiesPage = () => {
2-
return <></>
14+
return (
15+
<div className={cx('container')}>
16+
<Title label={'나의 전략'} />
17+
<ListHeader type="my" />
18+
<Suspense fallback={<div>Loading...</div>}>
19+
<MyStrategyList />
20+
</Suspense>
21+
</div>
22+
)
323
}
424

525
export default MyStrategiesPage
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.container {
2+
margin-top: 80px;
3+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { RefObject, useEffect } from 'react'
2+
3+
interface UseIntersectionObserverProps {
4+
ref: RefObject<HTMLElement>
5+
onIntersect: (entry: IntersectionObserverEntry) => void
6+
threshold?: number
7+
rootMargin?: string
8+
}
9+
10+
export const useIntersectionObserver = ({
11+
ref,
12+
onIntersect,
13+
threshold = 0.1,
14+
rootMargin = '0px',
15+
}: UseIntersectionObserverProps) => {
16+
useEffect(() => {
17+
if (!ref.current) return
18+
19+
const observer = new IntersectionObserver(
20+
(entries) => {
21+
entries.forEach((entry) => onIntersect(entry))
22+
},
23+
{
24+
threshold,
25+
rootMargin,
26+
}
27+
)
28+
29+
observer.observe(ref.current)
30+
31+
return () => {
32+
if (ref.current) {
33+
observer.unobserve(ref.current)
34+
}
35+
}
36+
}, [ref, threshold, rootMargin, onIntersect])
37+
}

shared/types/auth.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,10 @@ export interface TokenStatusModel {
5353

5454
export const isAdmin = (user: UserModel | null): boolean => {
5555
if (!user) return false
56-
return user.role.includes('admin')
56+
return user.role.includes('ADMIN')
5757
}
5858

5959
export const isTrader = (user: UserModel | null): boolean => {
6060
if (!user) return false
61-
return user.role.includes('trader')
61+
return user.role.includes('TRADER')
6262
}

0 commit comments

Comments
 (0)