diff --git a/app/(dashboard)/_ui/details-information/index.tsx b/app/(dashboard)/_ui/details-information/index.tsx index b1a3f095..54798185 100644 --- a/app/(dashboard)/_ui/details-information/index.tsx +++ b/app/(dashboard)/_ui/details-information/index.tsx @@ -12,9 +12,10 @@ const cx = classNames.bind(styles) interface Props { information: StrategyDetailsInformationModel + type?: 'default' | 'my' } -const DetailsInformation = ({ information }: Props) => { +const DetailsInformation = ({ information, type = 'default' }: Props) => { const percentageToArray = [ { percent: information.cumulativeProfitRate, label: '누적 수익률' }, { percent: information.maxDrawdownRate, label: '최대 자본 인하율' }, @@ -38,11 +39,13 @@ const DetailsInformation = ({ information }: Props) => { /> -
- {percentageToArray.map((data) => ( - - ))} -
+ {type === 'default' && ( +
+ {percentageToArray.map((data) => ( + + ))} +
+ )} ) } diff --git a/app/(dashboard)/_ui/introduction/styles.module.scss b/app/(dashboard)/_ui/introduction/styles.module.scss index 502c9827..773a3369 100644 --- a/app/(dashboard)/_ui/introduction/styles.module.scss +++ b/app/(dashboard)/_ui/introduction/styles.module.scss @@ -3,6 +3,7 @@ background-color: $color-white; border-radius: 5px; padding: 20px; + margin-bottom: 20px; .title { @include typo-b2; margin-bottom: 15px; diff --git a/app/(dashboard)/my/_api/get-my-strategy-list.ts b/app/(dashboard)/my/_api/get-my-strategy-list.ts index 3c072bcc..ca079c0c 100644 --- a/app/(dashboard)/my/_api/get-my-strategy-list.ts +++ b/app/(dashboard)/my/_api/get-my-strategy-list.ts @@ -13,7 +13,7 @@ interface StrategiesResponseModel { export const getMyStrategyList = async ({ page = 1, size = 4 }: { page: number; size: number }) => { const response = await axiosInstance.get( - `/api/my-strategies/page=${page}&size=${size}` + `/api/my-strategies?page=${page}&size=${size}` ) return response.data.result } diff --git a/app/(dashboard)/my/strategies/manage/[strategyId]/page.tsx b/app/(dashboard)/my/strategies/manage/[strategyId]/page.tsx index f3022b99..74049332 100644 --- a/app/(dashboard)/my/strategies/manage/[strategyId]/page.tsx +++ b/app/(dashboard)/my/strategies/manage/[strategyId]/page.tsx @@ -1,5 +1,65 @@ -const StrategyManagePage = () => { - return <> -} +'use client' + +import DetailsInformation from '@/app/(dashboard)/_ui/details-information' +import DetailsSideItem, { + InformationModel, + TitleType, +} from '@/app/(dashboard)/_ui/details-side-item' +import useGetDetailsInformationData from '@/app/(dashboard)/strategies/[strategyId]/_hooks/query/use-get-details-information-data' +import AnalysisContainer from '@/app/(dashboard)/strategies/[strategyId]/_ui/analysis-container' +import SubscriberItem from '@/app/(dashboard)/strategies/[strategyId]/_ui/subscriber-item' +import SideContainer from '@/app/(dashboard)/strategies/_ui/side-container' +import classNames from 'classnames/bind' + +import { useMSWStore } from '@/shared/stores/msw' +import { Button } from '@/shared/ui/button' +import BackHeader from '@/shared/ui/header/back-header' +import Title from '@/shared/ui/title' + +import styles from './styles.module.scss' + +const cx = classNames.bind(styles) +export type InformationType = { title: TitleType; data: string | number } | InformationModel[] + +const StrategyManagePage = ({ params }: { params: { strategyId: string } }) => { + const isReady = useMSWStore((state) => state.isReady) + const { data } = useGetDetailsInformationData({ + isReady, + strategyId: params.strategyId, + }) + + const { detailsSideData, detailsInformationData } = data || {} + + const hasDetailsSideData = detailsSideData?.map((data) => { + if (!Array.isArray(data)) return data.data !== undefined + }) + + return ( +
+ +
+ + <Button size="small" variant="filled" className={cx('edit-button')}> + 정보 수정하기 + </Button> + </div> + <div className={cx('strategy-container')}> + {detailsInformationData && ( + <DetailsInformation information={detailsInformationData} type="my" /> + )} + <AnalysisContainer type="my" /> + <SideContainer hasButton={true}> + <SubscriberItem subscribers={99} /> + {hasDetailsSideData?.[0] && + detailsSideData?.map((data, idx) => ( + <div key={`${data}_${idx}`}> + <DetailsSideItem information={data} /> + </div> + ))} + </SideContainer> + </div> + </div> + ) +} export default StrategyManagePage diff --git a/app/(dashboard)/my/strategies/manage/[strategyId]/styles.module.scss b/app/(dashboard)/my/strategies/manage/[strategyId]/styles.module.scss new file mode 100644 index 00000000..77611d8a --- /dev/null +++ b/app/(dashboard)/my/strategies/manage/[strategyId]/styles.module.scss @@ -0,0 +1,20 @@ +.container { + position: relative; +} + +.header { + display: flex; + justify-content: space-between; + + .edit-button { + width: $strategy-sidebar-width; + height: 40px; + } +} + +.strategy-container { + width: calc(100% - $strategy-sidebar-width); + max-width: $max-width; + padding-right: 10px; + margin-top: -10px; +} diff --git a/app/(dashboard)/my/strategies/page.tsx b/app/(dashboard)/my/strategies/page.tsx index 7010a8a5..9b5e093c 100644 --- a/app/(dashboard)/my/strategies/page.tsx +++ b/app/(dashboard)/my/strategies/page.tsx @@ -1,7 +1,13 @@ +'use client' + import { Suspense } from 'react' +import { useRouter } from 'next/navigation' + import classNames from 'classnames/bind' +import { PATH } from '@/shared/constants/path' +import { Button } from '@/shared/ui/button' import Title from '@/shared/ui/title' import ListHeader from '../../_ui/list-header' @@ -11,9 +17,18 @@ import styles from './styles.module.scss' const cx = classNames.bind(styles) const MyStrategiesPage = () => { + const router = useRouter() + const handleClick = () => { + router.push(PATH.ADD_STRATEGY) + } return ( <div className={cx('container')}> - <Title label={'나의 전략'} /> + <div className={cx('wrapper')}> + <Title label={'나의 전략'} /> + <Button size="small" variant="filled" onClick={handleClick}> + 전략 등록하기 + </Button> + </div> <ListHeader type="my" /> <Suspense fallback={<div>Loading...</div>}> <MyStrategyList /> diff --git a/app/(dashboard)/my/strategies/styles.module.scss b/app/(dashboard)/my/strategies/styles.module.scss index f481a8ff..ed53fc58 100644 --- a/app/(dashboard)/my/strategies/styles.module.scss +++ b/app/(dashboard)/my/strategies/styles.module.scss @@ -1,3 +1,10 @@ .container { margin-top: 80px; } + +.wrapper { + display: flex; + justify-content: space-between; + align-items: center; + padding-right: 20px; +} diff --git a/app/(dashboard)/strategies/[strategyId]/_ui/analysis-container/account-content.tsx b/app/(dashboard)/strategies/[strategyId]/_ui/analysis-container/account-content.tsx index 5cd51bf4..09718df7 100644 --- a/app/(dashboard)/strategies/[strategyId]/_ui/analysis-container/account-content.tsx +++ b/app/(dashboard)/strategies/[strategyId]/_ui/analysis-container/account-content.tsx @@ -4,6 +4,7 @@ import Image from 'next/image' import classNames from 'classnames/bind' +import { Button } from '@/shared/ui/button' import Pagination from '@/shared/ui/pagination' import styles from './styles.module.scss' @@ -21,9 +22,10 @@ interface Props { imagesData?: ImageDataModel[] currentPage: number onPageChange: (page: number) => void + isEditable?: boolean } -const AccountContent = ({ imagesData, currentPage, onPageChange }: Props) => { +const AccountContent = ({ imagesData, currentPage, onPageChange, isEditable = false }: Props) => { const croppedImagesData = imagesData?.slice( COUNT_PER_PAGE * (currentPage - 1), COUNT_PER_PAGE * (currentPage - 1) + COUNT_PER_PAGE @@ -33,6 +35,16 @@ const AccountContent = ({ imagesData, currentPage, onPageChange }: Props) => { return ( <div className={cx('table-wrapper')}> + {isEditable && ( + <div className={cx('edit-button-container')}> + <Button size="small" className={cx('edit-button')} variant="filled"> + 실계좌 이미지 업로드 + </Button> + <Button size="small" className={cx('delete-button')} variant="outline"> + 선택 삭제 + </Button> + </div> + )} {croppedImagesData && croppedImagesData.length !== 0 ? ( <> <div className={cx('account-images-container', isTwoLines && 'line')}> diff --git a/app/(dashboard)/strategies/[strategyId]/_ui/analysis-container/analysis-content.tsx b/app/(dashboard)/strategies/[strategyId]/_ui/analysis-container/analysis-content.tsx index ea5bd137..afd3726e 100644 --- a/app/(dashboard)/strategies/[strategyId]/_ui/analysis-container/analysis-content.tsx +++ b/app/(dashboard)/strategies/[strategyId]/_ui/analysis-container/analysis-content.tsx @@ -36,6 +36,7 @@ interface AnalysisContentProps { analysisData: DailyAnalysisModel[] | MonthlyAnalysisModel[] currentPage: number onPageChange: (page: number) => void + isEditable?: boolean } const AnalysisContent = ({ @@ -43,19 +44,39 @@ const AnalysisContent = ({ analysisData, currentPage, onPageChange, + isEditable = false, }: AnalysisContentProps) => { const tableHeader = type === 'daily' ? DAILY_TABLE_HEADER : MONTHLY_TABLE_HEADER return ( <div className={cx('table-wrapper', 'analysis')}> - <Button size="small" className={cx('excel-button')} variant="filled"> - 엑셀 다운받기 - </Button> + {!isEditable && ( + <Button size="small" className={cx('excel-button')} variant="filled"> + 엑셀 다운받기 + </Button> + )} + {isEditable && ( + <div className={cx('button-container')}> + <div className={cx('button-wrapper')}> + <Button size="small" className={cx('upload-button')} variant="filled"> + 엑셀 업로드 + </Button> + <Button size="small" className={cx('upload-button')} variant="outline"> + 직접 입력 + </Button> + </div> + <Button size="small" variant="filled"> + 전체 삭제 + </Button> + </div> + )} + <VerticalTable tableHead={tableHeader} tableBody={analysisData} currentPage={currentPage} countPerPage={COUNT_PER_PAGE} + isEditable={isEditable} /> <Pagination currentPage={currentPage} diff --git a/app/(dashboard)/strategies/[strategyId]/_ui/analysis-container/index.tsx b/app/(dashboard)/strategies/[strategyId]/_ui/analysis-container/index.tsx index 44b78ec1..7308bd17 100644 --- a/app/(dashboard)/strategies/[strategyId]/_ui/analysis-container/index.tsx +++ b/app/(dashboard)/strategies/[strategyId]/_ui/analysis-container/index.tsx @@ -16,7 +16,11 @@ const cx = classNames.bind(styles) type OptionsType = (typeof YAXIS_OPTIONS)[keyof typeof YAXIS_OPTIONS] -const AnalysisContainer = () => { +interface Props { + type?: 'default' | 'my' +} + +const AnalysisContainer = ({ type = 'default' }: Props) => { const [firstValue, setFirstValue] = useState<OptionsType>('잔고') const [secondValue, setSecondValue] = useState<OptionsType>('잔고') @@ -30,27 +34,30 @@ const AnalysisContainer = () => { return ( <div className={cx('container')}> <div className={cx('analysis-header')}> - <p>분석</p> - <div> - <Select - size="large" - options={options} - value={firstValue} - onChange={(newValue) => setFirstValue(newValue as OptionsType)} - /> - <Select - size="large" - options={options} - value={secondValue} - onChange={(newValue) => setSecondValue(newValue as OptionsType)} - /> - </div> + <p className={cx({ my: type === 'my' })}>분석</p> + {type === 'default' && ( + <div> + <Select + size="large" + options={options} + value={firstValue} + onChange={(newValue) => setFirstValue(newValue as OptionsType)} + /> + <Select + size="large" + options={options} + value={secondValue} + onChange={(newValue) => setSecondValue(newValue as OptionsType)} + /> + </div> + )} </div> - <div className={cx('chart-wrapper')}> - <AnalysisChart analysisChartData={analysisChartData} /> - </div> - - <TabsWithTable /> + {type === 'default' && ( + <div className={cx('chart-wrapper')}> + <AnalysisChart analysisChartData={analysisChartData} /> + </div> + )} + <TabsWithTable isEditable={type === 'my' ? true : false} /> </div> ) } diff --git a/app/(dashboard)/strategies/[strategyId]/_ui/analysis-container/styles.module.scss b/app/(dashboard)/strategies/[strategyId]/_ui/analysis-container/styles.module.scss index c538dcd5..c46c5823 100644 --- a/app/(dashboard)/strategies/[strategyId]/_ui/analysis-container/styles.module.scss +++ b/app/(dashboard)/strategies/[strategyId]/_ui/analysis-container/styles.module.scss @@ -8,6 +8,9 @@ p { @include typo-h4; color: $color-gray-600; + &.my { + margin-bottom: 40px; + } } div { display: flex; @@ -26,6 +29,15 @@ .table-wrapper { margin-top: 20px; position: relative; + .edit-button-container { + display: flex; + justify-content: space-between; + + .edit-button, + .delete-button { + padding: 7px 18px; + } + } &.analysis { margin-top: 40px; } @@ -73,3 +85,17 @@ color: $color-gray-600; @include typo-b2; } + +.button-container { + display: flex; + justify-content: space-between; + height: 30px; + margin-top: -20px; + margin-bottom: 10px; + + button.upload-button { + height: 100%; + padding: 7px 18px; + margin-right: 10px; + } +} diff --git a/app/(dashboard)/strategies/[strategyId]/_ui/analysis-container/tabs-width-table.tsx b/app/(dashboard)/strategies/[strategyId]/_ui/analysis-container/tabs-width-table.tsx index 38cb6fcf..0586aba8 100644 --- a/app/(dashboard)/strategies/[strategyId]/_ui/analysis-container/tabs-width-table.tsx +++ b/app/(dashboard)/strategies/[strategyId]/_ui/analysis-container/tabs-width-table.tsx @@ -16,7 +16,11 @@ import styles from './styles.module.scss' const cx = classNames.bind(styles) -const TabsWithTable = () => { +interface Props { + isEditable?: boolean +} + +const TabsWithTable = ({ isEditable = false }: Props) => { const [activeTab, setActiveTab] = useState('statistics') const [currentPage, setCurrentPage] = useState(1) @@ -43,6 +47,7 @@ const TabsWithTable = () => { analysisData={tableBody} currentPage={currentPage} onPageChange={handlePageChange} + isEditable={isEditable} /> ), }, @@ -69,6 +74,7 @@ const TabsWithTable = () => { imagesData={[]} currentPage={currentPage} onPageChange={handlePageChange} + isEditable={isEditable} /> </div> ), diff --git a/app/(dashboard)/strategies/[strategyId]/page.tsx b/app/(dashboard)/strategies/[strategyId]/page.tsx index 605d948a..e733e5bf 100644 --- a/app/(dashboard)/strategies/[strategyId]/page.tsx +++ b/app/(dashboard)/strategies/[strategyId]/page.tsx @@ -34,7 +34,7 @@ const StrategyDetailPage = ({ params }: { params: { strategyId: string } }) => { {detailsInformationData && <DetailsInformation information={detailsInformationData} />} <AnalysisContainer /> <ReviewContainer strategyId={params.strategyId} /> - <SideContainer isFixed={true}> + <SideContainer> <SubscriberItem subscribers={99} /> {hasDetailsSideData?.[0] && detailsSideData?.map((data, idx) => ( diff --git a/app/(dashboard)/strategies/_ui/side-container/index.tsx b/app/(dashboard)/strategies/_ui/side-container/index.tsx index 59939156..394d5e06 100644 --- a/app/(dashboard)/strategies/_ui/side-container/index.tsx +++ b/app/(dashboard)/strategies/_ui/side-container/index.tsx @@ -5,12 +5,12 @@ import styles from './styles.module.scss' const cx = classNames.bind(styles) interface Props { - isFixed?: boolean children: React.ReactNode + hasButton?: boolean } -const SideContainer = ({ isFixed = false, children }: Props) => { - return <aside className={cx('side-bar', { fixed: isFixed })}>{children}</aside> +const SideContainer = ({ children }: Props) => { + return <aside className={cx('side-bar')}>{children}</aside> } export default SideContainer diff --git a/app/(dashboard)/strategies/_ui/side-container/styles.module.scss b/app/(dashboard)/strategies/_ui/side-container/styles.module.scss index 294c8355..9a600262 100644 --- a/app/(dashboard)/strategies/_ui/side-container/styles.module.scss +++ b/app/(dashboard)/strategies/_ui/side-container/styles.module.scss @@ -3,8 +3,4 @@ position: absolute; right: 0px; top: 130px; - &.fixed { - position: fixed; - right: 28px; - } } diff --git a/next.config.mjs b/next.config.mjs index 2f1204bc..969da5ce 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -9,28 +9,22 @@ const nextConfig = { @import "@/shared/styles/base/functions"; `, }, - async rewrites() { - return [ - { - source: '/api/users/reissue/refreshtoken', - destination: '/api/users/reissue/refreshtoken', - }, - { - source: '/api/users/login', - destination: '/api/users/login', - }, - { - source: '/api/:path*', - destination: 'http://15.164.90.102:8081/api/:path*', - has: [ - { - type: 'header', - key: 'host' - } - ] - } - ] - }, + // async rewrites() { + // return [ + // { + // source: '/api/users/reissue/refreshtoken', + // destination: '/api/users/reissue/refreshtoken', + // }, + // { + // source: '/api/users/login', + // destination: '/api/users/login', + // }, + // { + // source: '/api/:path*', + // destination: 'http://15.164.90.102:8081/api/:path*', + // }, + // ] + // }, webpack: (config, { isServer }) => { if (isServer) { if (Array.isArray(config.resolve.alias)) { diff --git a/shared/api/axios.ts b/shared/api/axios.ts index 4de5f879..79c49a4d 100644 --- a/shared/api/axios.ts +++ b/shared/api/axios.ts @@ -8,7 +8,17 @@ import { getUserFromToken, isTokenExpired, refreshToken } from '@/shared/utils/t import { isAdmin } from '../types/auth' export const createAxiosInstance = (options: { withInterceptors?: boolean } = {}) => { - const instance = axios.create() + const instance = axios.create({ baseURL: 'http://15.164.90.102:8081' }) + + instance.interceptors.request.use((config) => { + if ( + config.url?.includes('/api/users/login') || + config.url?.includes('/api/users/reissue/refreshtoken') + ) { + config.baseURL = '' + } + return config + }) if (options.withInterceptors && typeof window !== 'undefined') { instance.interceptors.request.use( diff --git a/shared/constants/path.ts b/shared/constants/path.ts index a1b8b98f..3e244fa3 100644 --- a/shared/constants/path.ts +++ b/shared/constants/path.ts @@ -20,6 +20,7 @@ export const PATH = { FAVORITES: '/my/favorites', MY_STRATEGIES: '/my/strategies', STRATEGIES_MANAGE: '/my/strategies/manage', + ADD_STRATEGY: '/my/strategies/add', MY_QUESTIONS: '/my/questions', // Admin diff --git a/shared/ui/table/vertical/index.tsx b/shared/ui/table/vertical/index.tsx index 8e658582..2f4456fc 100644 --- a/shared/ui/table/vertical/index.tsx +++ b/shared/ui/table/vertical/index.tsx @@ -4,6 +4,7 @@ import { ReactNode } from 'react' import classNames from 'classnames/bind' import { DailyAnalysisModel, MonthlyAnalysisModel } from '@/shared/types/strategy-details-data' +import { Button } from '@/shared/ui/button' import sliceArray from '@/shared/utils/slice-array' import styles from './styles.module.scss' @@ -20,9 +21,16 @@ export interface VerticalTableProps { tableBody: TableBodyDataType[] countPerPage: number currentPage: number + isEditable?: boolean } -const VerticalTable = ({ tableHead, tableBody, countPerPage, currentPage }: VerticalTableProps) => { +const VerticalTable = ({ + tableHead, + tableBody, + countPerPage, + currentPage, + isEditable = false, +}: VerticalTableProps) => { const hasData = tableBody.length > 0 const slicedTableBody = sliceArray(tableBody, countPerPage, currentPage) @@ -34,6 +42,7 @@ const VerticalTable = ({ tableHead, tableBody, countPerPage, currentPage }: Vert {tableHead.map((head) => ( <td key={head}>{head}</td> ))} + {isEditable && <td></td>} </tr> </thead> {hasData && ( @@ -43,6 +52,16 @@ const VerticalTable = ({ tableHead, tableBody, countPerPage, currentPage }: Vert {Object.values(row).map((data, idx) => ( <td key={data + idx}>{data}</td> ))} + {isEditable && ( + <td className={cx('button-container')}> + <Button size="small" variant="outline" className={cx('edit-button')}> + 수정 + </Button> + <Button size="small" variant="filled" className={cx('delete-button')}> + 삭제 + </Button> + </td> + )} </tr> ))} </tbody> @@ -58,3 +77,4 @@ const VerticalTable = ({ tableHead, tableBody, countPerPage, currentPage }: Vert } export default VerticalTable + diff --git a/shared/ui/table/vertical/styles.module.scss b/shared/ui/table/vertical/styles.module.scss index 17ea63ed..b3b10445 100644 --- a/shared/ui/table/vertical/styles.module.scss +++ b/shared/ui/table/vertical/styles.module.scss @@ -1,12 +1,15 @@ .container { width: 100%; padding: 20px; + table { width: 100%; font-size: $text-c1; + thead { background-color: $color-gray-100; } + td { padding: 0 20px; height: 40px; @@ -15,6 +18,22 @@ border-top: 1px solid $color-gray-200; border-bottom: 1px solid $color-gray-200; } + + .button-container { + padding: 0; + text-align: right; + } + + .edit-button, + .delete-button { + min-width: 60px; + height: 24px; + padding: 7px 16px; + } + .edit-button { + border: 1px solid $color-gray-300; + margin-right: 8px; + } } }