diff --git a/README.md b/README.md index 488fbc69..d725604f 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,89 @@ -# InvestMetic - -

- - - - Storybook - - - - -

+## investmetic + +![investmetic](https://github.com/user-attachments/assets/04760f5b-1c52-48ee-9bd4-763b947e1899) + +
+ +๐Ÿ”— [investmetic](https://www.investmetic.co.kr/) + +**investmetic**์€ **ํˆฌ์ž ๋งค๋งค ์ „๋žต ๊ณต์œ  ๋ฐ +์ค‘๊ฐœ์†Œ์…œ ํ”Œ๋žซํผ ์„œ๋น„์Šค** ์ž…๋‹ˆ๋‹ค. + +## ํ…Œ์ŠคํŠธ ๊ณ„์ • + +| **์—ญํ• ** | **์ด๋ฉ”์ผ** | **๋น„๋ฐ€๋ฒˆํ˜ธ** | +| ------------ | -------------------- | ------------ | +| **ํˆฌ์ž์ž** | investor@example.com | investor123 | +| **ํŠธ๋ ˆ์ด๋”** | trader@example.com | trader123 | + +
+ +## ์—ญํ• ๋ถ„๋‹ด + +
+ +| [](https://github.com/devdeun) | [](https://github.com/nanafromjeju) | [](https://github.com/ssumanlife) | [](https://github.com/kimpra2989) | [](https://github.com/HSjjs98) | +| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| [๐Ÿ‘‘ @Deun](https://github.com/devdeun) | [@Nana](https://github.com/nanafromjeju) | [@SuMin](https://github.com/ssumanlife) | [@Kimpra](https://github.com/kimpra2989) | [@James](https://github.com/HSjjs98) | +| ํ”„๋กœ์ ํŠธ ์ดˆ๊ธฐ ์„ธํŒ…
(NextJS, ๋ฆฐํŠธ ๋ฐ Prettier,
๊ธฐ๋ณธ ์Šคํƒ€์ผ ๋ฐ SCSS, lefthook)

๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ
(์‚ฌ์ด๋“œ๋ฐ”, ํƒญ๋ฉ”๋‰ด, ์•„๋ฐ”ํƒ€,
ํ‘ธํ„ฐ, ๋กœ๋”ฉ์Šคํ”ผ๋„ˆ, ๋žœ๋”ฉ ๋ฉ”์ธ ์ฐจํŠธ)

๋žœ๋”ฉ ํŽ˜์ด์ง€,
ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€,
๊ตฌ๋…ํ•œ ์ „๋žต ํŽ˜์ด์ง€,
๋ฌธ์˜๋‚ด์—ญ ํŽ˜์ด์ง€,
์•ฝ๊ด€ ํŽ˜์ด์ง€
| ๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ
(์ธํ’‹, ๋ชจ๋‹ฌ)

ํ”„๋กœํ•„ ํŽ˜์ด์ง€
๊ณต์ง€์‚ฌํ•ญ ํŽ˜์ด์ง€
ํŠธ๋ ˆ์ด๋” ํŽ˜์ด์ง€
404 ํŽ˜์ด์ง€ | ๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ
(์ „๋žต ๋ฆฌ์ŠคํŠธ ์•„์ดํ…œ, ์ข…๋ชฉ&๋งค๋งค ์•„์ด์ฝ˜,
ํ…Œ์ด๋ธ”, ์Šค์ผˆ๋ ˆํ†ค, ํšŒ์›๊ฐ€์ž… ์Šคํ…,
์ „๋žต ์ •๋ณด, ๋ณ„์ , ๊ฒ€์ƒ‰๋ฐ”, ์‚ฌ์ด๋“œ ์ •๋ณด,
๋žญํ‚น ์ฐจํŠธ, ๋ถ„์„ ์ฐจํŠธ, ๋ชฉ๋ก ํ—ค๋”)

์ „๋žต ๋žญํ‚น ๋ชจ์Œ ํŽ˜์ด์ง€
์ „๋žต ์ƒ์„ธ ํŽ˜์ด์ง€ | ํ”„๋กœ์ ํŠธ ์ดˆ๊ธฐ ์„ธํŒ…(NextJS, ๋ฐฐํฌ, MSW)

๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ
(์…€๋ ‰ํŠธ, ํ—ค๋”)

๊ด€๋ฆฌ์ž ๊ณต์ง€ ํŽ˜์ด์ง€
๊ด€๋ฆฌ์ž ์งˆ๋ฌธ ํŽ˜์ด์ง€
๊ด€๋ฆฌ์ž ์‚ฌ์šฉ์ž ํŽ˜์ด์ง€ | ํ”„๋กœ์ ํŠธ ์ดˆ๊ธฐ ์„ธํŒ…
(Tanstack Query, MSW)

๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ
(ํŽ˜์ด์ง€๋„ค์ด์…˜, ๋ฒ„ํŠผ, ์ฒดํฌ ๋ฐ•์Šค, ๋žœ๋”ฉ ์„  ์ฐจํŠธ)

๋กœ๊ทธ์ธ ํŽ˜์ด์ง€
๋‚˜์˜ ์ „๋žต ํŽ˜์ด์ง€
์ „๋žต ๊ด€๋ฆฌ ํŽ˜์ด์ง€
์ „๋žต ๋“ฑ๋ก ํŽ˜์ด์ง€ | + +
+ +## ํŽ˜์ด์ง€ ์†Œ๊ฐœ + +![2024-12-148 40 55-ezgif com-video-to-gif-converter](https://github.com/user-attachments/assets/3fa3ecc3-90da-405a-984e-bd92f5c6d878) + +ํšŒ์›๊ฐ€์ž… ๋ฒ„ํŠผ์„ ์ƒ๋‹จ์— ๋ฐฐ์น˜ํ•˜์—ฌ ์ฆ‰๊ฐ์ ์ธ ๊ฐ€์ž…์„ ์œ ๋„ํ•˜๋ฉฐ, ์‚ฌ์ดํŠธ ์ด์šฉ์ž ์ˆ˜, ์ธ๊ธฐ ์ „๋žต, ํ†ตํ•ฉ ์ง€ํ‘œ(SM Score) ๋“ฑ ์ฃผ์š” ์ •๋ณด๋ฅผ ํ•œ๋ˆˆ์— ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +![2024-12-148 41 20-ezgif com-video-to-gif-converter](https://github.com/user-attachments/assets/2b07bbb5-8bac-42b8-855a-954ede75d697) + +์ „๋žต์„ ๋žญํ‚น ์ˆœ์œผ๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ๋กœ๊ทธ์ธํ•˜์ง€ ์•Š์€ ์‚ฌ์šฉ์ž๋„ ์ „๋žต ๋ชฉ๋ก์„ ์ž์œ ๋กญ๊ฒŒ ๋‘˜๋Ÿฌ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฒ€์ƒ‰๋ฐ”๋ฅผ ํ†ตํ•ด ๋งค๋งค ์œ ํ˜•, SM Score ๋“ฑ ๋‹ค์–‘ํ•œ ์กฐ๊ฑด์œผ๋กœ ์ „๋žต์„ ์‰ฝ๊ฒŒ ์ฐพ์•„๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +![2024-12-148 41 42-ezgif com-video-to-gif-converter](https://github.com/user-attachments/assets/9355e74c-adcf-42a1-94d5-2a429758c7c1) + +ํŠธ๋ ˆ์ด๋” ์ƒ์„ธ๋ณด๊ธฐ ํŽ˜์ด์ง€์—์„œ ํŠธ๋ ˆ์ด๋”์˜ ์ „๋žต์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +![2024-12-148 42 05-ezgif com-video-to-gif-converter](https://github.com/user-attachments/assets/76f83497-ad3d-419b-8d9f-9789b1edacf5) + +๋‚˜์˜ ์ „๋žต์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋ฌดํ•œ ์Šคํฌ๋กค๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋Š๊น€ ์—†์ด ํƒ์ƒ‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +![2024-12-148 42 28-ezgif com-video-to-gif-converter](https://github.com/user-attachments/assets/488d2806-37df-4902-9f03-77a332935911) + +๋‚ด๊ฐ€ ๊ตฌ๋…ํ•œ ์ „๋žต๋“ค์„ ํ•œ๋ˆˆ์— ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +![2024-12-148 42 48-ezgif com-video-to-gif-converter](https://github.com/user-attachments/assets/625ac057-0bae-4c65-b294-2b117d6364a4) + +๋ฌธ์˜ ๋‚ด์—ญ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋ชจ๋“  ๋‹ต๋ณ€, ๋‹ต๋ณ€ ๋Œ€๊ธฐ, ๋‹ต๋ณ€ ์™„๋ฃŒ ์ƒํƒœ๋กœ ๊ตฌ๋ถ„๋ฉ๋‹ˆ๋‹ค. ์ •๋ ฌ๊ณผ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ์„ ํ†ตํ•ด ์›ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์‰ฝ๊ฒŒ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +![2024-12-148 43 13-ezgif com-video-to-gif-converter](https://github.com/user-attachments/assets/68aad565-dc14-4028-ba78-b08686159ed2) + +๊ด€๋ฆฌ์ž ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธํ•˜๋ฉด ํšŒ์› ๊ด€๋ฆฌ, ๊ณต์ง€์‚ฌํ•ญ ๋“ฑ๋ก, ์ข…๋ชฉ ๋ฐ ๋งค๋งค ์œ ํ˜• ๊ด€๋ฆฌ, ์ „๋žต ์Šน์ธ ๊ด€๋ฆฌ, ๋ฌธ์˜ ๋‚ด์—ญ ํ™•์ธ ๋“ฑ์˜ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋ฉฐ, ํšจ์œจ์ ์ธ ์‚ฌ์ดํŠธ ์šด์˜์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. + +## ๐Ÿ›  ๊ธฐ์ˆ  ์Šคํƒ + +| ๊ธฐ์ˆ  ์Šคํƒ | ๋„์ž… ์ด์œ  | +| -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | +| | (14.2.16) SSR๋กœ SEO์™€ ์ดˆ๊ธฐ ๋กœ๋”ฉ ์†๋„ ๊ฐœ์„ , ํด๋” ๊ธฐ๋ฐ˜ ๋ผ์šฐํŒ…์œผ๋กœ ๊ฒฝ๋กœ ์ž๋™ ์ƒ์„ฑ | +| | (8.4.0) ๋ฌธ์„œํ™”๋กœ ์‚ฌ์šฉ ๋ฐฉ๋ฒ• ๋ฐ ๋””์ž์ธ ์‹œ์Šคํ…œ ํ™•์ธ, UI ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์ฆ‰๊ฐ ํ™•์ธํ•˜๋ฉฐ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ƒ๋žต ๊ฐ€๋Šฅ | +| | (1.80.5) Mixin์œผ๋กœ ๋ฐ˜๋ณต ์Šคํƒ€์ผ ์žฌ์‚ฌ์šฉ ํšจ์œจ์„ฑ ์ฆ๋Œ€, ๋ณ€์ˆ˜ ์ง€์›์œผ๋กœ ์ƒ‰์ƒยทํฐํŠธ ๋“ฑ ๊ณตํ†ต ๊ฐ’ ๊ด€๋ฆฌ ์šฉ์ด | +| ![TypeScript](https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white) | (5.0) ์ •์  ํƒ€์ž… ๊ฒ€์‚ฌ๋กœ ์•ˆ์ •์„ฑ ํ™•๋ณด ๋ฐ ๋Ÿฐํƒ€์ž„ ์—๋Ÿฌ ๊ฐ์†Œ, ์ฝ”๋“œ ์ž๋™์™„์„ฑ๊ณผ ๋ช…ํ™•ํ•œ ํƒ€์ž… ์ •์˜๋กœ ๊ฐ€๋…์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ ํ–ฅ์ƒ ๊ด€๋ฆฌ | +| ![React](https://img.shields.io/badge/react-%2320232a.svg?style=for-the-badge&logo=react&logoColor=%2361DAFB) | (18) ์ปดํฌ๋„ŒํŠธ ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜๋กœ ์žฌ์‚ฌ์šฉ์„ฑ ๊ทน๋Œ€ํ™”, ์„ ์–ธํ˜• UI๋กœ ์ง๊ด€์ ์ด๊ณ  ํšจ์œจ์ ์ธ ๊ฐœ๋ฐœ ๊ฒฝํ—˜ ์ œ๊ณต | +| ![TanStack Query](https://img.shields.io/badge/tanstack--query-FF4154?style=for-the-badge&logo=reactquery&logoColor=white) | (5.59.19) ๋น„๋™๊ธฐ ์ƒํƒœ ๊ด€๋ฆฌ์™€ ์บ์‹ฑ์œผ๋กœ ๋ฐ์ดํ„ฐ ์š”์ฒญ ์ตœ์ ํ™” ๋ฐ ์„œ๋ฒ„ ์ƒํƒœ ๊ด€๋ฆฌ ๊ฐ„์†Œํ™” | +| ![Zustand](https://img.shields.io/badge/zustand-2759C6.svg?style=for-the-badge&logo=zustand&logoColor=white) | (5.0.1) ๊ฐ€๋ฒผ์šด ์ƒํƒœ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ์ง๊ด€์ ์ธ API์™€ ๋ถˆ๋ณ€์„ฑ ์—†์ด๋„ ํšจ์œจ์ ์ธ ์ƒํƒœ ๊ด€๋ฆฌ ์ œ๊ณต | + +
+ +## ํด๋” ๊ตฌ์กฐ + +![fsdแ„‘แ…ฉแ†ฏแ„ƒแ…ฅแ„€แ…ฎแ„Œแ…ฉ](https://github.com/user-attachments/assets/4afd073c-b1e2-469f-9c49-cab5cffd74a4) + +#### FSD(Feature Sliced Design)์˜ ์žฅ์  + +**1. ๋ชจ๋“ˆํ™”์™€ ๋…๋ฆฝ์„ฑ:** ๊ธฐ๋Šฅ๋ณ„๋กœ ํŒŒ์ผ์„ ๊ด€๋ฆฌํ•ด ์ˆ˜์ •, ์‚ญ์ œ, ์ถ”๊ฐ€๊ฐ€ ์šฉ์ด
+**2. ๋ช…ํ™•ํ•œ ๊ตฌ์กฐ:** ๊ธฐ๋Šฅ ๋‹จ์œ„๋กœ ํด๋”๊ฐ€ ๊ตฌ์„ฑ๋˜์–ด ํŒŒ์ผ์„ ์‰ฝ๊ฒŒ ์ฐพ์„ ์ˆ˜ ์žˆ์Œ
+**3. ํ˜‘์—… ํšจ์œจ์„ฑ:** ์ž‘์—… ์˜์—ญ์ด ๋ถ„๋ฆฌ๋˜์–ด ์ถฉ๋Œ ์—†์ด ๋™์‹œ ์ž‘์—… ๊ฐ€๋Šฅ
+**4. ์œ ์ง€๋ณด์ˆ˜ ์šฉ์ด์„ฑ:** ๊ด€๋ จ ์ฝ”๋“œ๊ฐ€ ํ•œ ํด๋”์— ๋ชจ์—ฌ ์žˆ์–ด ์ˆ˜์ • ๋ฐ ํ™•์žฅ์ด ์‰ฌ์›€ + +## ํƒ€์ž„๋ผ์ธ + +![แ„แ…กแ„‹แ…ตแ†ทแ„…แ…กแ„‹แ…ตแ†ซ](https://github.com/user-attachments/assets/ff6909a0-f2b5-45f6-8247-05309e1f3ab2) diff --git a/app/(dashboard)/_ui/analysis-container/account-content.tsx b/app/(dashboard)/_ui/analysis-container/account-content.tsx index 5b157a51..639641b1 100644 --- a/app/(dashboard)/_ui/analysis-container/account-content.tsx +++ b/app/(dashboard)/_ui/analysis-container/account-content.tsx @@ -8,6 +8,7 @@ import classNames from 'classnames/bind' import { ACCOUNT_PAGE_COUNT } from '@/shared/constants/count-per-page' import useModal from '@/shared/hooks/custom/use-modal' +import { ImageDataModel } from '@/shared/types/strategy-data' import { Button } from '@/shared/ui/button' import Checkbox from '@/shared/ui/check-box' import AccountImageModal from '@/shared/ui/modal/account-image-modal' @@ -22,12 +23,6 @@ import styles from './styles.module.scss' const cx = classNames.bind(styles) -export interface ImageDataModel { - id: number - imageUrl: string - title: string -} - interface Props { strategyId: number currentPage: number @@ -80,12 +75,12 @@ const AccountContent = ({ strategyId, currentPage, onPageChange, isEditable = fa imageIds: selectedImages, }) setSelectedImages([]) - } catch (error) { - console.error('Failed to delete images:', error) + } catch (err) { + console.error('Failed to delete images:', err) } } - if (!data || !Array.isArray(data.content) || isLoading) return null + if (!Array.isArray(data?.content) || isLoading) return null const imagesData = data.content const croppedImagesData: ImageDataModel[] = sliceArray( @@ -95,6 +90,7 @@ const AccountContent = ({ strategyId, currentPage, onPageChange, isEditable = fa ) const isTwoLines = (croppedImagesData?.length || 0) > 4 + return (
{isEditable && ( @@ -118,7 +114,7 @@ const AccountContent = ({ strategyId, currentPage, onPageChange, isEditable = fa
)} - {croppedImagesData && croppedImagesData.length !== 0 ? ( + {croppedImagesData?.length > 0 ? ( <>
{croppedImagesData?.map((imageData: ImageDataModel) => ( diff --git a/app/(dashboard)/_ui/analysis-container/analysis-chart.tsx b/app/(dashboard)/_ui/analysis-container/analysis-chart.tsx index 8eff7139..dd8785b5 100644 --- a/app/(dashboard)/_ui/analysis-container/analysis-chart.tsx +++ b/app/(dashboard)/_ui/analysis-container/analysis-chart.tsx @@ -5,8 +5,8 @@ import dynamic from 'next/dynamic' import classNames from 'classnames/bind' import Highcharts, { SeriesOptionsType } from 'highcharts' +import { CHART_SELECT_OPTIONS } from './constants' import styles from './styles.module.scss' -import { YAXIS_OPTIONS } from './yaxis-options' const HighchartsReact = dynamic(() => import('highcharts-react-official'), { ssr: false, @@ -14,7 +14,7 @@ const HighchartsReact = dynamic(() => import('highcharts-react-official'), { const cx = classNames.bind(styles) -type YAxisType = keyof typeof YAXIS_OPTIONS +type YAxisType = keyof typeof CHART_SELECT_OPTIONS interface AnalysisChartDataModel { dates: string[] @@ -30,23 +30,23 @@ interface Props { const AnalysisChart = ({ analysisChartData: data }: Props) => { const getOptionName = (sequence: number) => { const key = Object.keys(data.data)[sequence] as YAxisType | undefined - return key ? YAXIS_OPTIONS[key] : '' + return key ? CHART_SELECT_OPTIONS[key] : '' } + if (!data) return
+ const chartOptions: Highcharts.Options = { chart: { type: 'areaspline', height: 367, backgroundColor: 'transparent', - margin: [10, 60, 10, 60], + margin: [10, 75, 10, 75], zoomType: 'x', } as Highcharts.ChartOptions, title: { text: undefined }, xAxis: { visible: false, categories: data.dates, - min: data.dates.length > 30 ? data.dates.length - 30 : 0, - max: data.dates.length - 1, }, yAxis: [ { @@ -86,7 +86,7 @@ const AnalysisChart = ({ analysisChartData: data }: Props) => { align: 'left', verticalAlign: 'top', layout: 'vertical', - x: 40, + x: 70, y: -10, itemStyle: { color: '#4D4D4D', diff --git a/app/(dashboard)/_ui/analysis-container/analysis-content.tsx b/app/(dashboard)/_ui/analysis-container/analysis-content.tsx index 43b53475..c7a19a5e 100644 --- a/app/(dashboard)/_ui/analysis-container/analysis-content.tsx +++ b/app/(dashboard)/_ui/analysis-container/analysis-content.tsx @@ -3,40 +3,27 @@ import { useState } from 'react' import classNames from 'classnames/bind' import { ANALYSIS_PAGE_COUNT } from '@/shared/constants/count-per-page' +import useDelayedLoading from '@/shared/hooks/custom/use-delayed-loading' import useModal from '@/shared/hooks/custom/use-modal' +import { MyDailyAnalysisModel } from '@/shared/types/strategy-data' import { Button } from '@/shared/ui/button' import AnalysisUploadModal from '@/shared/ui/modal/analysis-upload-modal' +import DailyAnalysisDeleteAllModal from '@/shared/ui/modal/daily-analysis-delete-all-modal' +import EditAnalysisModal from '@/shared/ui/modal/edit-daily-analysis-modal.ts' import Pagination from '@/shared/ui/pagination' -import VerticalTable from '@/shared/ui/table/vertical' +import VerticalTable, { TableBodyDataType } from '@/shared/ui/table/vertical' import { useAnalysisUploadMutation } from '../../my/_hooks/query/use-analysis-mutation' import useGetMyDailyAnalysis from '../../my/_hooks/query/use-get-my-daily-analysis' +import { useMyAnalysisMutation } from '../../my/_hooks/query/use-manage-daily-analysis' import useGetAnalysis from '../../strategies/[strategyId]/_hooks/query/use-get-analysis' import useGetAnalysisDownload from '../../strategies/[strategyId]/_hooks/query/use-get-analysis-download' +import { generateFormattedStrategyData } from '../../strategies/[strategyId]/util' +import { DAILY_TABLE_HEADER, MONTHLY_TABLE_HEADER } from './constants' import styles from './styles.module.scss' const cx = classNames.bind(styles) -const DAILY_TABLE_HEADER = [ - '๋‚ ์งœ', - '์›๊ธˆ', - '์ž…์ถœ๊ธˆ', - '์ผ ์†์ต', - '์ผ ์†์ต๋ฅ ', - '๋ˆ„์  ์†์ต', - '๋ˆ„์  ์ˆ˜์ต๋ฅ ', -] - -const MONTHLY_TABLE_HEADER = [ - '๋‚ ์งœ', - '์›๊ธˆ', - '์ž…์ถœ๊ธˆ', - '์›” ์†์ต', - '์›” ์†์ต๋ฅ ', - '๋ˆ„์  ์†์ต', - '๋ˆ„์  ์ˆ˜์ต๋ฅ ', -] - interface Props { type: 'daily' | 'monthly' strategyId: number @@ -45,6 +32,18 @@ interface Props { isEditable?: boolean } +const isMyAnalysisData = (data: TableBodyDataType): data is MyDailyAnalysisModel => { + if (!data || typeof data !== 'object' || Array.isArray(data)) return false + + return ( + 'dailyAnalysisId' in data && + 'dailyDate' in data && + 'transaction' in data && + 'dailyProfitLoss' in data && + 'principal' in data + ) +} + const AnalysisContent = ({ type, strategyId, @@ -52,18 +51,29 @@ const AnalysisContent = ({ onPageChange, isEditable = false, }: Props) => { + const COLWIDTH = [1.7, 1.7, 1.3, 1.5, 1, 1.5, 1] const { mutate } = useGetAnalysisDownload() - const [uploadType, setUploadType] = useState<'excel' | 'direct' | null>(null) + const [selectedAnalysis, setSelectedAnalysis] = useState(null) + const { isModalOpen, openModal, closeModal } = useModal() + const { + isModalOpen: isDeleteModalOpen, + openModal: openDeleteModal, + closeModal: closeDeleteModal, + } = useModal() + const { + isModalOpen: isEditModalOpen, + openModal: openEditModal, + closeModal: closeEditModal, + } = useModal() - //TODO ํ˜„์žฌ ๋‚˜์˜ ์ „๋žต ์ผ๊ฐ„๋ถ„์„ ์กฐํšŒ ๊ถŒํ•œ์ด ์—†์–ด์„œ ์•ˆ๋ณด์ž„ - const { data: myAnalysisData } = useGetMyDailyAnalysis( + const { data: myAnalysisData, isLoading: isMyAnalysisLoading } = useGetMyDailyAnalysis( strategyId, currentPage, ANALYSIS_PAGE_COUNT ) - const { data: publicAnalysisData } = useGetAnalysis( + const { data: publicAnalysisData, isLoading: isPublicAnalysisLoading } = useGetAnalysis( strategyId, type, currentPage, @@ -71,19 +81,22 @@ const AnalysisContent = ({ ) const analysisData = isEditable ? myAnalysisData : publicAnalysisData + const isLoading = isEditable ? isMyAnalysisLoading : isPublicAnalysisLoading + const isDelayedLoading = useDelayedLoading(isLoading, 500) - const { deleteAllAnalysis, isLoading } = useAnalysisUploadMutation( + const analysisContent = generateFormattedStrategyData(analysisData?.content) + + const { deleteAllAnalysis, isLoading: isDeleteAllLoading } = useAnalysisUploadMutation( strategyId, currentPage, ANALYSIS_PAGE_COUNT ) + const { deleteAnalysisData } = useMyAnalysisMutation(strategyId, currentPage, ANALYSIS_PAGE_COUNT) const handleDownload = () => { mutate({ strategyId, type }) } - const tableHeader = type === 'daily' ? DAILY_TABLE_HEADER : MONTHLY_TABLE_HEADER - const handleExcelUpload = () => { setUploadType('excel') openModal() @@ -99,20 +112,61 @@ const AnalysisContent = ({ setUploadType(null) } + const handleCloseEditModal = () => { + closeEditModal() + setSelectedAnalysis(null) + } + const handleDeleteAll = async () => { - if (window.confirm('๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?')) { - try { - await deleteAllAnalysis() - } catch (error) { - console.error('Delete error:', error) - alert('๋ฐ์ดํ„ฐ ์‚ญ์ œ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.') - } + try { + await deleteAllAnalysis() + closeDeleteModal() + } catch (err) { + console.error('Delete error:', err) } } + const handleDeleteAnalysis = async (dailyAnalysisId: number) => { + try { + await deleteAnalysisData(dailyAnalysisId) + } catch (err) { + console.error('Delete failed:', err) + } + } + + const renderActions = (row: TableBodyDataType) => { + if (!isMyAnalysisData(row)) return null + + return ( +
+ + +
+ ) + } + + const tableHeader = type === 'daily' ? DAILY_TABLE_HEADER : MONTHLY_TABLE_HEADER + return ( -
- {!isEditable && analysisData && ( +
+ {!isEditable && analysisData?.content.length > 0 && (
-
)} - {analysisData ? ( + {isDelayedLoading ? ( + + ) : analysisData?.content.length > 0 ? ( <> )} + + {selectedAnalysis && ( + + )} + +
) } diff --git a/app/(dashboard)/_ui/analysis-container/yaxis-options.ts b/app/(dashboard)/_ui/analysis-container/constants.ts similarity index 65% rename from app/(dashboard)/_ui/analysis-container/yaxis-options.ts rename to app/(dashboard)/_ui/analysis-container/constants.ts index a9be1ddf..38b37f2c 100644 --- a/app/(dashboard)/_ui/analysis-container/yaxis-options.ts +++ b/app/(dashboard)/_ui/analysis-container/constants.ts @@ -1,4 +1,4 @@ -export const YAXIS_OPTIONS = { +export const CHART_SELECT_OPTIONS = { BALANCE: '์ž”๊ณ ', PRINCIPAL: '์›๊ธˆ', CUMULATIVE_TRANSACTION_AMOUNT: '๋ˆ„์  ์ž…์ถœ ๊ธˆ์•ก', @@ -17,3 +17,23 @@ export const YAXIS_OPTIONS = { TOTAL_PROFIT: '์ด ์ด์ต', TOTAL_LOSS: '์ด ์†์‹ค', } as const + +export const DAILY_TABLE_HEADER = [ + '๋‚ ์งœ', + '์›๊ธˆ', + '์ž…์ถœ๊ธˆ', + '์ผ ์†์ต', + '์ผ ์†์ต๋ฅ ', + '๋ˆ„์  ์†์ต', + '๋ˆ„์  ์ˆ˜์ต๋ฅ ', +] + +export const MONTHLY_TABLE_HEADER = [ + '๋‚ ์งœ', + '์›๊ธˆ', + '์ž…์ถœ๊ธˆ', + '์›” ์†์ต', + '์›” ์†์ต๋ฅ ', + '๋ˆ„์  ์†์ต', + '๋ˆ„์  ์ˆ˜์ต๋ฅ ', +] diff --git a/app/(dashboard)/_ui/analysis-container/example.ts b/app/(dashboard)/_ui/analysis-container/example.ts deleted file mode 100644 index c1b1289d..00000000 --- a/app/(dashboard)/_ui/analysis-container/example.ts +++ /dev/null @@ -1,123 +0,0 @@ -export const analysisChartData = { - dates: ['2023-01-01', '2023-01-02', '2023-01-03', '2023-01-04', '2023-01-05', '2023-01-06'], - data: { - CURRENT_DRAWDOWN: [2000, 5660, 4000, 9000, 7000, 10000], - PRINCIPAL: [50000, 60000, 80000, 70000, 80000, 90000], - }, -} - -export const statisticsData = { - assetManagement: { - balance: 896217437, // ์ž”๊ณ  - cumulativeTransactionAmount: 896217437, // ๋ˆ„์  ๊ฑฐ๋ž˜ ๊ธˆ์•ก - principal: 238704360, // ์›๊ธˆ - operationPeriod: '2๋…„ 4์›”', // ์šด์šฉ ๊ธฐ๊ฐ„ - startDate: '2012-10-11', // ์‹œ์ž‘ ์ผ์ž - endDate: '2015-03-11', // ์ข…๋ฃŒ ์ผ์ž (endDate) - daysSincePeakUpdate: 513, // ๊ณ ์  ๊ฐฑ์‹  ํ›„ ๊ฒฝ๊ณผ์ผ - }, - profitLoss: { - cumulativeProfitAmount: 247525031, // ๋ˆ„์  ์ˆ˜์ต ๊ธˆ์•ก - cumulativeProfitRate: 49.24, // ๋ˆ„์  ์ˆ˜์ต๋ฅ  - maxCumulativeProfitAmount: 247525031, // ์ตœ๋Œ€ ๋ˆ„์  ์ˆ˜์ต ๊ธˆ์•ก - maxCumulativeProfitRate: 49.24, // ์ตœ๋Œ€ ๋ˆ„์  ์ˆ˜์ต๋ฅ  - averageProfitLossAmount: 336311, // ํ‰๊ท  ์†์ต ๊ธˆ์•ก - averageProfitLossRate: 6, // ํ‰๊ท  ์†์ต๋ฅ  - maxDailyProfitAmount: 25257250, // ์ตœ๋Œ€ ์ผ ์ˆ˜์ต ๊ธˆ์•ก - maxDailyProfitRate: 3.985, // ์ตœ๋Œ€ ์ผ ์ˆ˜์ต๋ฅ  - maxDailyLossAmount: -17465050, // ์ตœ๋Œ€ ์ผ ์†์‹ค ๊ธˆ์•ก - maxDailyLossRate: -3.95, // ์ตœ๋Œ€ ์ผ ์†์‹ค๋ฅ  - roa: 453, // ์ž์‚ฐ ์ˆ˜์ต๋ฅ  (Return on Assets) - profitFactor: 1.48, // Profit Factor - }, - ddMddInfo: { - currentDrawdown: 0, // ํ˜„์žฌ ์ž๋ณธ ์ธํ•˜ ๊ธˆ์•ก - currentDrawdownRate: 0, // ํ˜„์žฌ ์ž๋ณธ ์ธํ•˜์œจ - maxDrawdown: -54832778, // ์ตœ๋Œ€ ์ž๋ณธ ์ธํ•˜ ๊ธˆ์•ก - maxDrawdownRate: -13.98, // ์ตœ๋Œ€ ์ž๋ณธ ์ธํ•˜์œจ - }, - tradingInfo: { - totalTradeDays: 736, // ์ด ๊ฑฐ๋ž˜ ์ผ์ˆ˜ - totalProfitableDays: 508, // ์ด ์ด์ต ์ผ์ˆ˜ - totalLossDays: 228, // ์ด ์†์‹ค ์ผ์ˆ˜ - currentConsecutiveLossDays: 6, // ํ˜„์žฌ ์—ฐ์† ์†์‹ค ์ผ์ˆ˜ - maxConsecutiveProfitDays: 22, // ์ตœ๋Œ€ ์—ฐ์† ์ด์ต ์ผ์ˆ˜ - maxConsecutiveLossDays: 8, // ์ตœ๋Œ€ ์—ฐ์† ์†์‹ค ์ผ์ˆ˜ - winRate: 69, // ์Šน๋ฅ  - }, -} - -export const tableBody = [ - { - date: '2015-03-12', // ๋‚ ์งœ - principal: 100000000, // ์›๊ธˆ - transaction: 0, // ์ž…์ถœ๊ธˆ - dailyProfitLoss: 332410, // ์ผ ์†์ต - dailyProfitLossRate: 0.33, // ์ผ ์ˆ˜์ต๋ฅ  - cumulativeProfitLoss: 302280, // ๋ˆ„์  ์†์ต - cumulativeProfitLossRate: 0.3, // ๋ˆ„์  ์ˆ˜์ต๋ฅ  - }, - { - date: '2015-03-13', - principal: 100000000, - transaction: 0, - dailyProfitLoss: 332410, - dailyProfitLossRate: 0.33, - cumulativeProfitLoss: 302280, - cumulativeProfitLossRate: 0.3, - }, - { - date: '2015-03-14', - principal: 100000000, - transaction: 0, - dailyProfitLoss: 332410, - dailyProfitLossRate: 0.33, - cumulativeProfitLoss: 302280, - cumulativeProfitLossRate: 0.3, - }, - { - date: '2015-03-15', - principal: 100000000, - transaction: 0, - dailyProfitLoss: 332410, - dailyProfitLossRate: 0.33, - cumulativeProfitLoss: 302280, - cumulativeProfitLossRate: 0.3, - }, - { - date: '2015-03-16', - principal: 100000000, - transaction: 0, - dailyProfitLoss: 332410, - dailyProfitLossRate: 0.33, - cumulativeProfitLoss: 302280, - cumulativeProfitLossRate: 0.3, - }, - { - date: '2015-03-17', - principal: 100000000, - transaction: 0, - dailyProfitLoss: 332410, - dailyProfitLossRate: 0.33, - cumulativeProfitLoss: 302280, - cumulativeProfitLossRate: 0.3, - }, - { - date: '2015-03-18', - principal: 100000000, - transaction: 0, - dailyProfitLoss: 332410, - dailyProfitLossRate: 0.33, - cumulativeProfitLoss: 302280, - cumulativeProfitLossRate: 0.3, - }, - { - date: '2015-03-19', - principal: 100000000, - transaction: 0, - dailyProfitLoss: 332410, - dailyProfitLossRate: 0.33, - cumulativeProfitLoss: 302280, - cumulativeProfitLossRate: 0.3, - }, -] diff --git a/app/(dashboard)/_ui/analysis-container/index.tsx b/app/(dashboard)/_ui/analysis-container/index.tsx index c6422eff..e3b74cfc 100644 --- a/app/(dashboard)/_ui/analysis-container/index.tsx +++ b/app/(dashboard)/_ui/analysis-container/index.tsx @@ -8,13 +8,13 @@ import Select from '@/shared/ui/select' import useGetAnalysisChart from '../../strategies/[strategyId]/_hooks/query/use-get-analysis-chart' import AnalysisChart from './analysis-chart' +import { CHART_SELECT_OPTIONS } from './constants' import styles from './styles.module.scss' import TabsWithTable from './tabs-width-table' -import { YAXIS_OPTIONS } from './yaxis-options' const cx = classNames.bind(styles) -export type AnalysisChartOptionsType = keyof typeof YAXIS_OPTIONS +export type AnalysisChartOptionsType = keyof typeof CHART_SELECT_OPTIONS interface Props { strategyId: number @@ -27,12 +27,9 @@ const AnalysisContainer = ({ strategyId, type = 'default' }: Props) => { useState('CUMULATIVE_PROFIT_LOSS') const { data: chartData } = useGetAnalysisChart({ strategyId, firstOption, secondOption }) - const optionsToArray = Object.entries(YAXIS_OPTIONS) - const options: { value: string; label: string }[] = [] - - for (const [key, value] of optionsToArray) { - options.push({ value: key, label: value }) - } + const options = Object.entries(CHART_SELECT_OPTIONS).map((option) => { + return { value: option[0], label: option[1] } + }) return (
diff --git a/app/(dashboard)/_ui/analysis-container/statistics-content.tsx b/app/(dashboard)/_ui/analysis-container/statistics-content.tsx index b70db4fb..85d3befe 100644 --- a/app/(dashboard)/_ui/analysis-container/statistics-content.tsx +++ b/app/(dashboard)/_ui/analysis-container/statistics-content.tsx @@ -14,7 +14,7 @@ interface StatisticsDataModel { } interface Props { - statisticsData: StatisticsDataModel + statisticsData?: StatisticsDataModel } const StatisticsContent = ({ statisticsData }: Props) => { diff --git a/app/(dashboard)/_ui/analysis-container/styles.module.scss b/app/(dashboard)/_ui/analysis-container/styles.module.scss index e139a8e9..a5d5f9df 100644 --- a/app/(dashboard)/_ui/analysis-container/styles.module.scss +++ b/app/(dashboard)/_ui/analysis-container/styles.module.scss @@ -2,6 +2,7 @@ padding: 20px; border-radius: 5px; background-color: $color-white; + .analysis-header { display: flex; justify-content: space-between; @@ -29,21 +30,29 @@ .table-wrapper { margin-top: 20px; position: relative; + .edit-button-container { display: flex; justify-content: space-between; margin-bottom: 30px; + .edit-button, .delete-button { padding: 7px 18px; } + .delete-button:disabled { background-color: transparent; } } + &.analysis { margin-top: 40px; } + &.my-analysis { + margin-top: 20px; + } + .excel-button { height: 30px; position: absolute; @@ -57,15 +66,18 @@ grid-template-columns: repeat(5, 1fr); grid-template-rows: repeat(1, 1fr); column-gap: 20px; + &.line { grid-template-rows: repeat(2, 1fr); row-gap: 10px; } + .image-data { display: flex; flex-direction: column; justify-content: center; align-items: center; + .image { position: relative; width: 100%; @@ -74,11 +86,13 @@ cursor: pointer; overflow: hidden; } + span { text-align: center; @include typo-c1; } } + .title-wrapper { display: flex; justify-content: center; @@ -100,13 +114,23 @@ .button-container { display: flex; justify-content: space-between; + align-items: center; height: 30px; - margin-top: -20px; - margin-bottom: 10px; + gap: 8px; + padding: 0 10px; - button.upload-button { - height: 100%; + button.upload-button, + button.delete-all-button { padding: 7px 18px; margin-right: 10px; } + + button.delete-button, + button.edit-button { + min-width: 60px; + height: 24px; + padding: 7px 16px; + border-radius: 16px; + border: 1px solid $color-gray-300; + } } diff --git a/app/(dashboard)/_ui/details-information/index.tsx b/app/(dashboard)/_ui/details-information/index.tsx index 3aae89cd..5a08238e 100644 --- a/app/(dashboard)/_ui/details-information/index.tsx +++ b/app/(dashboard)/_ui/details-information/index.tsx @@ -14,9 +14,17 @@ interface Props { strategyId: number information: StrategyDetailsInformationModel type?: 'default' | 'my' + isEditable?: boolean + error?: Error } -const DetailsInformation = ({ strategyId, information, type = 'default' }: Props) => { +const DetailsInformation = ({ + strategyId, + information, + type = 'default', + error, + isEditable = false, +}: Props) => { const percentageToArray = [ { percent: information.cumulativeProfitRate, label: '๋ˆ„์  ์ˆ˜์ต๋ฅ ' }, { percent: information.maxDrawdownRate, label: '์ตœ๋Œ€ ์ž๋ณธ ์ธํ•˜์œจ' }, @@ -29,6 +37,7 @@ const DetailsInformation = ({ strategyId, information, type = 'default' }: Props <>
- + {type === 'default' && (
{percentageToArray.map((data) => ( diff --git a/app/(dashboard)/_ui/details-information/invest-information.tsx b/app/(dashboard)/_ui/details-information/invest-information.tsx index 9a9a78a8..5a51b8cf 100644 --- a/app/(dashboard)/_ui/details-information/invest-information.tsx +++ b/app/(dashboard)/_ui/details-information/invest-information.tsx @@ -8,16 +8,17 @@ interface Props { stock: string[] trade: string cycle: string + isEditable?: boolean } -const InvestInformation = ({ stock, trade, cycle }: Props) => { +const InvestInformation = ({ stock, trade, cycle, isEditable = false }: Props) => { const investData = [ { title: 'ํˆฌ์ž ์ข…๋ชฉ', data: stock.join(',') }, { title: '๋งค๋งค ์œ ํ˜•', data: trade }, { title: 'ํˆฌ์ž ์ฃผ๊ธฐ', data: cycle }, ] return ( -
+
{investData.map((data, idx) => (

{data.title}

diff --git a/app/(dashboard)/_ui/details-information/strategy-name-box.tsx b/app/(dashboard)/_ui/details-information/strategy-name-box.tsx index ff29ebe5..08b509c4 100644 --- a/app/(dashboard)/_ui/details-information/strategy-name-box.tsx +++ b/app/(dashboard)/_ui/details-information/strategy-name-box.tsx @@ -1,8 +1,17 @@ -import React from 'react' +'use client' + +import { useEffect, useRef, useState } from 'react' import StrategiesIcon from '@/app/(dashboard)/_ui/strategies-item/strategies-icon' +import { FileIcon } from '@/public/icons' +import { TrashcanIcon } from '@/public/icons' import classNames from 'classnames/bind' +import { SUPPORTED_FILE_TYPES } from '@/shared/constants/supported-file-types' +import Input from '@/shared/ui/input' + +import useGetProposalFileName from '../../my/_hooks/query/use-get-proposal-file-name' +import useEditInformationStore from '../../my/strategies/manage/[strategyId]/_store/use-edit-information-store' import useGetProposalDownload from '../../strategies/[strategyId]/_hooks/query/use-get-proposal-download' import styles from './styles.module.scss' @@ -10,23 +19,132 @@ const cx = classNames.bind(styles) interface Props { strategyId: number + name: string + hasProposal: boolean iconUrls?: string[] iconNames?: string[] - name: string + isEditable?: boolean + error?: Error } -const StrategyNameBox = ({ strategyId, iconUrls, iconNames, name }: Props) => { +const StrategyNameBox = ({ + strategyId, + name, + hasProposal: initialHasProposal, + iconUrls, + iconNames, + isEditable = false, + error, +}: Props) => { + const information = useEditInformationStore((state) => state.information) + const proposal = useEditInformationStore((state) => state.proposal) + const setStrategyName = useEditInformationStore((state) => state.actions.setStrategyName) + const setProposalFile = useEditInformationStore((state) => state.actions.setProposalFile) + const initializeProposal = useEditInformationStore((state) => state.actions.initializeProposal) + const setProposalModified = useEditInformationStore((state) => state.actions.setProposalModified) + + const { refetch } = useGetProposalFileName(strategyId) const { mutate } = useGetProposalDownload() + const fileInputRef = useRef(null) + const [selectedFile, setSelectedFile] = useState(null) + const [fileError, setFileError] = useState('') const handleDownload = () => { mutate({ strategyId, name }) } + const handleFileChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0] + const supportedTypes = SUPPORTED_FILE_TYPES + + if (file && supportedTypes.includes(file.type)) { + setSelectedFile(file) + setProposalFile(file) + setFileError('') + } else { + setSelectedFile(null) + setProposalFile(null) + setFileError('์ง€์›๋˜๋Š” ํŒŒ์ผ ํ˜•์‹์ด ์•„๋‹™๋‹ˆ๋‹ค.') + } + } + + const handleProposalClick = () => { + if (fileInputRef.current) { + fileInputRef.current.click() + } + } + + const handleProposalDelete = () => { + if (selectedFile || proposal.proposalFileName) { + setSelectedFile(null) + setProposalFile(null) + initializeProposal('') + setProposalModified(true) + setFileError('') + } + } + + useEffect(() => { + setStrategyName(name) + }, [name, setStrategyName]) + + useEffect(() => { + if (isEditable) { + refetch().then((response) => { + const fileName = response.data?.result?.proposalFileName + if (fileName) { + initializeProposal(fileName) + } + }) + } + }, [isEditable, refetch, initializeProposal]) + + const displayFileName = + selectedFile?.name || proposal.proposalFileName || '๋“ฑ๋ก๋œ ์ œ์•ˆ์„œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค' + return (
- -

{name}

- +
+ + {isEditable ? ( + ) => setStrategyName(e.target.value)} + inputSize="small" + className={cx('name-input')} + maxLength={16} + value={information.strategyName as string} + /> + ) : ( +

{name}

+ )} + {initialHasProposal && !isEditable && ( + + )} +
+ {isEditable && ( +
+ +
+ + + +
+ {error &&

{error.message}

} + {fileError &&

{fileError}

} +
+ )}
) } diff --git a/app/(dashboard)/_ui/details-information/styles.module.scss b/app/(dashboard)/_ui/details-information/styles.module.scss index 46224894..4e0ebbbd 100644 --- a/app/(dashboard)/_ui/details-information/styles.module.scss +++ b/app/(dashboard)/_ui/details-information/styles.module.scss @@ -5,22 +5,100 @@ } .name-container { - width: 30%; + width: 35%; height: 144px; - padding: 20px; + padding: 18px 10px; display: flex; + justify-content: center; flex-direction: column; - gap: 10px; + gap: 4px; border-radius: 5px; background-color: $color-white; - .name { - @include typo-h4; + + .name-section { + .name-input { + padding: 10px; + height: 30px; + width: 45%; + margin: 6px 0; + color: $color-gray-700; + } + .name { + @include typo-b1; + margin: 10px 0; + } + button { + height: 30px; + width: 100%; + border-radius: 8px; + background-color: $color-gray-200; + color: $color-gray-700; + } + } + + .proposal-section { + width: 100%; + + .file-input { + display: none; + } + + .proposal-input-wrapper { + position: relative; + width: 100%; + + .file-name-input { + width: 100%; + height: 30px; + background-color: $color-gray-200; + border: 0; + border-radius: 8px; + cursor: default; + color: $color-gray-700; + padding-right: 70px; + text-overflow: ellipsis; + padding-left: 16px; + } + + .proposal-button { + position: absolute; + right: 16px; + top: 50%; + transform: translateY(-50%); + display: flex; + align-items: center; + justify-content: center; + color: $color-gray-700; + background: none; + @include typo-b3; + + svg { + width: 20px; + height: 20px; + } + + &.modify { + right: 40px; + } + + &.delete { + right: 12px; + } + } + } + + .error-message { + @include typo-c1; + color: $color-orange-700; + margin-top: 8px; + } } - button { - height: 36px; - border-radius: 8px; - background-color: $color-gray-100; - color: $color-gray-700; + + @include tablet-md { + gap: 2px; + button { + font-size: $text-b3; + } } } @@ -29,14 +107,20 @@ height: 144px; display: flex; gap: 20px; - padding: 30px; + padding: 20px; border-radius: 5px; background-color: $color-white; + + &.edit { + background-color: $color-gray-200; + } + .info-item { width: 100%; height: 100%; border-right: 1px solid $color-gray-200; padding-right: 4px; + overflow-y: auto; &:last-child { border-right: 0; } @@ -48,6 +132,16 @@ @include typo-b3; color: $color-gray-700; margin-top: 16px; + @include tablet-md { + font-size: $text-c1; + } + } + &::-webkit-scrollbar { + width: 6px; + } + &::-webkit-scrollbar-thumb { + background-color: $color-gray-200; + border-radius: 10px; } } } @@ -62,6 +156,7 @@ width: 20%; height: 108px; border-radius: 5px; + gap: 8px; background-color: $color-white; display: flex; flex-direction: column; @@ -71,12 +166,24 @@ .label { @include typo-b2; color: $color-gray-800; + @include tablet-md { + font-size: $text-b3; + } + @include tablet-sm { + font-size: $text-c1; + } } .percent { - @include typo-h3; + @include typo-h4; color: #f53500; &.minus { color: #6877ff; } + @include tablet-md { + font-size: $text-b1; + } + @include tablet-sm { + font-size: $text-b2; + } } } diff --git a/app/(dashboard)/_ui/details-side-item/index.tsx b/app/(dashboard)/_ui/details-side-item/index.tsx index c550f81a..75d520ac 100644 --- a/app/(dashboard)/_ui/details-side-item/index.tsx +++ b/app/(dashboard)/_ui/details-side-item/index.tsx @@ -25,12 +25,14 @@ interface Props { profileImage?: string isMyStrategy?: boolean strategyName?: string + isEditable?: boolean } const DetailsSideItem = ({ strategyId, information, profileImage, + isEditable = false, isMyStrategy = true, strategyName, }: Props) => { @@ -38,12 +40,12 @@ const DetailsSideItem = ({ return ( <> {isArray ? ( -
+
{information.map((item) => (
{item.title}
-

{item.data}

+

{typeof item.data === 'number' ? item.data.toFixed(2) : item.data}

))} @@ -56,6 +58,7 @@ const DetailsSideItem = ({ profileImage={profileImage} isMyStrategy={isMyStrategy} strategyName={strategyName} + isEditable={isEditable} /> )} diff --git a/app/(dashboard)/_ui/details-side-item/side-item.tsx b/app/(dashboard)/_ui/details-side-item/side-item.tsx index 265bac8e..e132a209 100644 --- a/app/(dashboard)/_ui/details-side-item/side-item.tsx +++ b/app/(dashboard)/_ui/details-side-item/side-item.tsx @@ -1,10 +1,7 @@ 'use client' -import { usePathname, useRouter } from 'next/navigation' - import classNames from 'classnames/bind' -import { PATH } from '@/shared/constants/path' import useModal from '@/shared/hooks/custom/use-modal' import { useAuthStore } from '@/shared/stores/use-auth-store' import Avatar from '@/shared/ui/avatar' @@ -25,6 +22,7 @@ interface Props { profileImage?: string isMyStrategy?: boolean strategyName?: string + isEditable?: boolean } const SideItem = ({ @@ -34,6 +32,7 @@ const SideItem = ({ profileImage, isMyStrategy = false, strategyName, + isEditable = false, }: Props) => { const { isModalOpen: isAddQuestionModalOpen, @@ -46,17 +45,11 @@ const SideItem = ({ closeModal: guideCloseModal, } = useModal() const user = useAuthStore((state) => state.user) - const router = useRouter() - const path = usePathname() - - const handleRouter = () => { - router.push(`${PATH.MY_STRATEGIES}/manage/${strategyId}`) - } const isTrader = user?.role.includes('TRADER') return ( -
+
{title}
{title === 'ํŠธ๋ ˆ์ด๋”' ? ( @@ -66,15 +59,10 @@ const SideItem = ({

{data}

{!isMyStrategy && !isTrader && ( - )} - {isMyStrategy && !path.includes('my') && ( - - )} ) : (

{formatNumber(data)}

diff --git a/app/(dashboard)/_ui/details-side-item/styles.module.scss b/app/(dashboard)/_ui/details-side-item/styles.module.scss index b4393c12..530e01ab 100644 --- a/app/(dashboard)/_ui/details-side-item/styles.module.scss +++ b/app/(dashboard)/_ui/details-side-item/styles.module.scss @@ -15,10 +15,15 @@ .side-item, .side-items { - width: 276px; + width: 100%; background-color: $color-white; border-radius: 5px; margin-bottom: 20px; + + &.edit { + background-color: $color-gray-200; + } + .title { font-weight: $text-bold; font-size: $text-b2; @@ -42,5 +47,13 @@ align-items: center; p { margin: 0 4px 0 11px; + @include tablet-md { + margin: 0 2px 0 4px; + } } } + +.trader-button { + height: 30px; + padding: 0; +} diff --git a/app/(dashboard)/_ui/introduction/index.tsx b/app/(dashboard)/_ui/introduction/index.tsx index b9ee51cd..6e578a34 100644 --- a/app/(dashboard)/_ui/introduction/index.tsx +++ b/app/(dashboard)/_ui/introduction/index.tsx @@ -1,27 +1,41 @@ 'use client' +/* eslint-disable react-hooks/exhaustive-deps */ import { useEffect, useRef, useState } from 'react' import { CloseIcon, OpenIcon } from '@/public/icons' import classNames from 'classnames/bind' +import Textarea from '@/shared/ui/textarea' + +import useEditInformationStore from '../../my/strategies/manage/[strategyId]/_store/use-edit-information-store' import styles from './styles.module.scss' const cx = classNames.bind(styles) interface Props { content: string + isEditable?: boolean } -const StrategyIntroduction = ({ content }: Props) => { +const StrategyIntroduction = ({ content, isEditable = false }: Props) => { const [shouldShowMore, setShouldShowMore] = useState(false) + const [editContent, setEditContent] = useState(null) const [isOverflow, setIsOverflow] = useState(false) - const contentRef = useRef(null) + const contentRef = useRef(null) + const setDescription = useEditInformationStore((state) => state.actions.setDescription) useEffect(() => { checkOverflow() + setEditContent(content) }, [content]) + useEffect(() => { + if (editContent) { + setDescription(editContent as string) + } + }, [editContent]) + const checkOverflow = () => { if (contentRef.current) { setIsOverflow(contentRef.current.scrollHeight > contentRef.current.offsetHeight) @@ -32,7 +46,15 @@ const StrategyIntroduction = ({ content }: Props) => {

์ „๋žต ์ƒ์„ธ ์†Œ๊ฐœ

-

{content}

+ {isEditable ? ( +