diff --git a/public/assets/images/decrease.svg b/public/assets/images/decrease.svg new file mode 100644 index 0000000..4b4d0c9 --- /dev/null +++ b/public/assets/images/decrease.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/assets/images/increase.svg b/public/assets/images/increase.svg new file mode 100644 index 0000000..68958b3 --- /dev/null +++ b/public/assets/images/increase.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/assets/images/search.svg b/public/assets/images/search.svg new file mode 100644 index 0000000..81a626c --- /dev/null +++ b/public/assets/images/search.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/components/common/graphs/normalGraph/NormalGraph.jsx b/src/components/common/graphs/normalGraph/NormalGraph.jsx index 84b60de..868b2e5 100644 --- a/src/components/common/graphs/normalGraph/NormalGraph.jsx +++ b/src/components/common/graphs/normalGraph/NormalGraph.jsx @@ -22,7 +22,7 @@ const sketch = (p5) => { const lineDelay = 0 * 60; // 0.3초 딜레이 (프레임 단위, 60fps 기준) let axisProgress = 0; let auxLine1Progress = 0; - const axisStep = 0.03; + const axisStep = 0.02; let auxLine1Length = 0; let auxLine2Length = 0; let auxLine3Length = 0; @@ -31,14 +31,19 @@ const sketch = (p5) => { let drawnLines = 0; let zoom = 1; let color = []; + let width = 400; p5.setup = () => { p5.noLoop(); // 초기에는 멈춰 있는 상태로 설정 - p5.createCanvas(600, 400); + p5.createCanvas(width + 200, 400); }; p5.updateWithProps = (props) => { p5.noLoop(); // 초기에는 멈춰 있는 상태로 설정 - + if (props.width) { + width = props.width; + } + //todo 페이지 이동 시 새로고침되게 되면 createCanvas 주석처리 하기! + p5.createCanvas(width + 200, 400); if (props.data) { data = props.data; barProgress = Array(data.length).fill(0); @@ -95,19 +100,19 @@ const sketch = (p5) => { for (let i = 1; i <= 4; i++) { const y = 350 - i * 62.5; - if (i == 4) p5.line(40, y, 40 + auxLine1Length, y); + if (i == 4) p5.line(40, y, width - 360 + auxLine1Length, y); else if (i == 3) { - p5.line(40, y, 40 + auxLine2Length, y); + p5.line(40, y, width - 360 + auxLine2Length, y); } else if (i == 2) { - p5.line(40, y, 40 + auxLine3Length, y); + p5.line(40, y, width - 360 + auxLine3Length, y); } else if (i == 1) { - p5.line(40, y, 40 + auxLine4Length, y); + p5.line(40, y, width - 360 + auxLine4Length, y); } } } if (axisProgress < 1) { if (axisProgress < 1) axisProgress += axisStep; - const xAxisLength = p5.lerp(0, 500, axisProgress); + const xAxisLength = p5.lerp(0, 100 + width, axisProgress); const yAxisLength = p5.lerp(0, 300, axisProgress); p5.strokeWeight(4); p5.stroke(color[0], color[1], color[2]); @@ -142,10 +147,10 @@ const sketch = (p5) => { p5.strokeWeight(4); p5.stroke(color[0], color[1], color[2]); p5.fill(color[0], color[1], color[2]); - p5.line(30, 350, 550, 350); + p5.line(30, 350, width + 150, 350); p5.line(50, 370, 50, 50); p5.triangle(50, 45, 45, 53, 55, 53); - p5.triangle(555, 350, 547, 345, 547, 355); + p5.triangle(width + 155, 350, width + 147, 345, width + 147, 355); // 보조선 값 텍스트 표시 p5.drawingContext.shadowBlur = 0; @@ -163,17 +168,23 @@ const sketch = (p5) => { p5.stroke(color[0], color[1], color[2]); for (let i = 0; i < drawnLines; i++) { - const startX = p5.map(i, 0, data.length - 1, 100, 500); + const startX = p5.map(i, 0, data.length - 1, 100, width + 100); const startY = p5.map(data[i], 0, 100, 300, 50); - const endX = p5.map(i + 1, 0, data.length - 1, 100, 500); + const endX = p5.map(i + 1, 0, data.length - 1, 100, width + 100); const endY = p5.map(data[i + 1], 0, 100, 300, 50); p5.line(startX, startY, endX, endY); } if (drawnLines < data.length) { - const startX = p5.map(drawnLines, 0, data.length - 1, 100, 500); + const startX = p5.map(drawnLines, 0, data.length - 1, 100, width + 100); const startY = p5.map(data[drawnLines], 0, 100, 300, 50); - const endX = p5.map(drawnLines + 1, 0, data.length - 1, 100, 500); + const endX = p5.map( + drawnLines + 1, + 0, + data.length - 1, + 100, + width + 100 + ); const endY = p5.map(data[drawnLines + 1], 0, 100, 300, 50); // console.log(drawnLines, lineProgress); if (lineProgress == 1) { @@ -196,13 +207,13 @@ const sketch = (p5) => { // 막대 그래프 그리기 for (let i = 0; i < data.length; i++) { - const barHeight = p5.map(data[i], 0, Math.max(...data), 0, 125); + const barHeight = p5.map(data[i], 0, Math.max(...data), 0, 250); currentBarHeight[i] = p5.lerp(0, barHeight, barProgress[i]); if ( p5.mouseX > - p5.map(i, 0, data.length - 1, 100, 500) - barWidth * 0.5 && + p5.map(i, 0, data.length - 1, 100, width + 100) - barWidth * 0.5 && p5.mouseX < - p5.map(i, 0, data.length - 1, 100, 500) + barWidth * 0.5 && + p5.map(i, 0, data.length - 1, 100, width + 100) + barWidth * 0.5 && p5.mouseY > 350 - currentBarHeight[i] && p5.mouseY < 350 ) { @@ -214,9 +225,9 @@ const sketch = (p5) => { p5.fill(color[0], color[1], color[2], 100); if ( p5.mouseX > - p5.map(i, 0, data.length - 1, 100, 500) - barWidth * 0.5 && + p5.map(i, 0, data.length - 1, 100, width + 100) - barWidth * 0.5 && p5.mouseX < - p5.map(i, 0, data.length - 1, 100, 500) + barWidth * 0.5 && + p5.map(i, 0, data.length - 1, 100, width + 100) + barWidth * 0.5 && p5.mouseY > 350 - currentBarHeight[i] && p5.mouseY < 350 ) { @@ -226,15 +237,15 @@ const sketch = (p5) => { } if ( p5.mouseX > - p5.map(i, 0, data.length - 1, 100, 500) - barWidth * 0.5 && + p5.map(i, 0, data.length - 1, 100, width + 100) - barWidth * 0.5 && p5.mouseX < - p5.map(i, 0, data.length - 1, 100, 500) + barWidth * 0.5 && + p5.map(i, 0, data.length - 1, 100, width + 100) + barWidth * 0.5 && p5.mouseY > 350 - currentBarHeight[i] && p5.mouseY < 350 ) { p5.fill(255, 255, 255, 20); p5.rect( - p5.map(i, 0, data.length - 1, 100, 500) - 35, + p5.map(i, 0, data.length - 1, 100, width + 100) - 35, 350 - barHeight - 12 - 25, 70, 35, @@ -246,19 +257,19 @@ const sketch = (p5) => { if (data[i] == 100) p5.text( data[i], - p5.map(i, 0, data.length - 1, 100, 500) - 9, + p5.map(i, 0, data.length - 1, 100, width + 100) - 9, 350 - barHeight - 18 ); else { p5.text( data[i], - p5.map(i, 0, data.length - 1, 100, 500) - 6, + p5.map(i, 0, data.length - 1, 100, width + 100) - 6, 350 - barHeight - 21 ); p5.fill(100, 100, 100, 255); p5.text( date[i], - p5.map(i, 0, data.length - 1, 100, 500) - 22, + p5.map(i, 0, data.length - 1, 100, width + 100) - 22, 350 - barHeight - 8 ); } @@ -267,7 +278,7 @@ const sketch = (p5) => { p5.fill(color[0], color[1], color[2], 50); } p5.rect( - p5.map(i, 0, data.length - 1, 100, 500) - barWidth * 0.5, + p5.map(i, 0, data.length - 1, 100, width + 100) - barWidth * 0.5, 350 - currentBarHeight[i], barWidth, currentBarHeight[i], @@ -350,13 +361,22 @@ const sketch = (p5) => { }; }; -const NormalGraph = ({ data, date, lineSpeed, barSpeed, color, zoom }) => { +const NormalGraph = ({ + data, + date, + lineSpeed, + barSpeed, + color, + zoom, + width, +}) => { useEffect(() => { window.noLoop = false; return () => { window.noLoop = true; }; }, []); + console.log(data); return ( { barSpeed={barSpeed} color={color} zoom={zoom} + width={width} /> ); diff --git a/src/components/common/header/Header.jsx b/src/components/common/header/Header.jsx index 0a88608..60f7c94 100644 --- a/src/components/common/header/Header.jsx +++ b/src/components/common/header/Header.jsx @@ -3,7 +3,7 @@ import { StyledHeaderDiv, StyledHeaderParentDiv } from "./Header.style"; import Search from "../search/Search"; import { useSelector } from "react-redux"; -export default function Header(props) { +export default function Header() { return ( diff --git a/src/components/common/news/keyword-blog/Keyword.blog.jsx b/src/components/common/news/keyword-blog/Keyword.blog.jsx index 46054a8..a396de8 100644 --- a/src/components/common/news/keyword-blog/Keyword.blog.jsx +++ b/src/components/common/news/keyword-blog/Keyword.blog.jsx @@ -18,8 +18,6 @@ function timeAgo(dateString) { const minute = parseInt(dateString.slice(10, 12), 10); const second = parseInt(dateString.slice(12, 14), 10); - console.log(`${year}-${month}-${day}`); - const articleDate = new Date(year, month, day, hour, minute, second); const differenceInMilliseconds = now - articleDate; const differenceInHours = Math.floor( diff --git a/src/pages/main/Main.jsx b/src/pages/main/Main.jsx index c2b7d05..c71816d 100644 --- a/src/pages/main/Main.jsx +++ b/src/pages/main/Main.jsx @@ -1,12 +1,14 @@ -import { useState } from "react"; import Header from "../../components/common/header/Header"; import Sidebar from "../../components/common/sidebar/Sidebar"; import { StyledMainDiv, MainBody, MainContent } from "./Main.style"; -import RelatedStock from "./RelatedStock"; +import RelatedStockContent from "./RelatedStockContent"; import { useSelector } from "react-redux"; +import SearchContent from "./SearchContent"; +import { useState } from "react"; +import ScrollToTopButton from "../../components/common/scroll-top-button/Scroll.top.button"; export default function MainPage() { - const keyword= useSelector((state) => state.keyword.keyword); + const keyword = useSelector((state) => state.keyword.keyword); return ( @@ -14,7 +16,8 @@ export default function MainPage() {
- + + diff --git a/src/pages/main/Main.style.js b/src/pages/main/Main.style.js index a6d0e6b..471829e 100644 --- a/src/pages/main/Main.style.js +++ b/src/pages/main/Main.style.js @@ -3,13 +3,12 @@ import styled from "styled-components"; export const StyledMainDiv = styled.div` display: flex; width: 100vw; - height: 100vh; + height: 100%; `; export const MainBody = styled.div` display: flex; width: 100%; - height: 100%; flex-direction: column; align-items: center; justify-contents: center; @@ -19,7 +18,8 @@ export const MainContent = styled.div` display: block; flex: 1; width: 100vw; - height: 100%; + height: auto; + overflow-y: scroll; `; export const StyledMainContentDiv = styled.div` diff --git a/src/pages/main/RelatedStock.style.js b/src/pages/main/RelatedStock.style.js deleted file mode 100644 index 7f6d4dc..0000000 --- a/src/pages/main/RelatedStock.style.js +++ /dev/null @@ -1,14 +0,0 @@ -import styled from "styled-components"; - -export const StyledMainContentDiv = styled.div` - width: 90%; - background-color: white; - display: flex; - flex-direction: column; - box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); - padding: 15px; - border-radius: 20px; - margin-bottom: 30px; - overflow-x: auto; - white-space: nowrap; -`; diff --git a/src/pages/main/RelatedStock.jsx b/src/pages/main/RelatedStockContent.jsx similarity index 87% rename from src/pages/main/RelatedStock.jsx rename to src/pages/main/RelatedStockContent.jsx index 9accc59..af01187 100644 --- a/src/pages/main/RelatedStock.jsx +++ b/src/pages/main/RelatedStockContent.jsx @@ -1,11 +1,11 @@ import { useState, useEffect } from "react"; -import { StyledMainContentDiv } from "./RelatedStock.style"; import ContentHeader from "./contents-item/ContentHeader"; import StockContent from "./contents-item/StockContent"; import { Contents, StyledContentsDiv } from "./contents-item/Contents.style"; import Skeleton from "react-loading-skeleton"; import "react-loading-skeleton/dist/skeleton.css"; import axios from "axios"; +import { StyledMainContentDiv } from "./Main.style"; export default function RelatedStock({ keyword }) { const STOCK_SIZE = 6; @@ -15,6 +15,7 @@ export default function RelatedStock({ keyword }) { const [companies, setCompaines] = useState([]); useEffect(() => { + setCompaines([]); const fetchData = async () => { try { const response = await axios.get(`/api/company?word=${keyword}`); @@ -28,24 +29,26 @@ export default function RelatedStock({ keyword }) { } }; fetchData(); - }, []); + }, [keyword]); return ( {/* 내용이 들어오면 변경 */} {companies.length === 0 - ? Array.from({ length: STOCK_SIZE }).map(() => ( + ? Array.from({ length: STOCK_SIZE }).map((elem, index) => ( { + console.log(`Fetching data for keyword: ${keyword}`); + const response = await axios.get("/api/trends", { + params: { + keyword: keyword, + }, + }); + return JSON.parse(response.data); +}; + +export default function SearchContent({ keyword }) { + const [percent, setPercent] = useState(0); + const [currentWeekData, setCurrentWeekData] = useState([]); + const [currentWeekDates, setCurrentWeekDates] = useState([]); + + const { + data: stockData, + isLoading, + error, + } = useQuery(["stockData", keyword], () => fetchStockData(keyword), { + staleTime: Infinity, + enabled: !!keyword, + }); + + useEffect(() => { + if (stockData) { + const dayOfData = stockData.default.timelineData.sort((a, b) => { + return b.time - a.time; + }); + + let currentWeek = 0; + let lastWeek = 0; + + const currentWeekValues = dayOfData + .slice(0, 7) + .reverse() + .map((elem) => { + currentWeek += parseFloat(elem.formattedValue); + return parseFloat(elem.formattedValue); + }); + + const currentWeekTimes = dayOfData + .slice(0, 7) + .reverse() + .map((elem) => { + console.log("dfs", elem.formattedTime); + return elem.formattedTime; + }); + + dayOfData + .slice(7, 14) + .reverse() + .map((elem) => { + lastWeek += parseFloat(elem.formattedValue); + }); + + const calculatedPercent = ((currentWeek - lastWeek) / lastWeek) * 100; + + setPercent(Math.round(calculatedPercent * 100) / 100); + setCurrentWeekData(currentWeekValues); + setCurrentWeekDates(currentWeekTimes); + } + }, [stockData]); + + if (error) return
Error loading data
; + + return ( + + + {percent > 0 && ( + <> +

+ 의 검색량이 전 주에 비해 + + {Math.abs(Math.round(percent))}% + +

+ increase + 증가했어요. + + )} + {percent < 0 && ( + <> +

+ 의 검색량이 전 주에 비해 + + {Math.abs(percent)}% + +

+ decrease + 감소했어요. + + )} + {percent === 0 && <>의 이번주 검색량이 전 주와 동일해요.} + + ) : ( + <> +

의 검색량을 불러오는 중이에요...

+ + ) + } + toLink="/main/social" + /> + + {isLoading ? ( +
+ +
+ ) : ( + + )} +
+
+ ); +} diff --git a/src/pages/main/contents-item/ContentHeader.jsx b/src/pages/main/contents-item/ContentHeader.jsx index c14d5ef..8e2d88d 100644 --- a/src/pages/main/contents-item/ContentHeader.jsx +++ b/src/pages/main/contents-item/ContentHeader.jsx @@ -16,10 +16,11 @@ export default function ContentHeader(props) { }} > - + "{props.keyword}" @@ -35,7 +36,7 @@ export default function ContentHeader(props) { display: "flex", }} > - + - + ); diff --git a/src/pages/main/contents-item/ContentHeader.style.js b/src/pages/main/contents-item/ContentHeader.style.js index 81d9358..820cd6b 100644 --- a/src/pages/main/contents-item/ContentHeader.style.js +++ b/src/pages/main/contents-item/ContentHeader.style.js @@ -19,7 +19,7 @@ export const GlowIcon = styled.div` transition: filter 0.3s ease-in-out; &:hover { - filter: drop-shadow(0 0 3px rgba(0, 83, 122, 0.8)); + filter: drop-shadow(0 0 3px rgba(0, 83, 122, 0.3)); } } `; diff --git a/src/pages/main/contents-item/Contents.style.js b/src/pages/main/contents-item/Contents.style.js index 732a27a..ce99866 100644 --- a/src/pages/main/contents-item/Contents.style.js +++ b/src/pages/main/contents-item/Contents.style.js @@ -64,9 +64,9 @@ export const StyledContentsMiniTitle = styled.div` font-size: 0.9rem !important; font-weight: 500; color: ${(props) => - props.signPerYesterday > 0 + props.signperyesterday > 0 ? "blue" - : props.signPerYesterday < 0 + : props.signperyesterday < 0 ? "orangered" : "black"}; } diff --git a/src/pages/main/contents-item/StockContent.jsx b/src/pages/main/contents-item/StockContent.jsx index 8073a4c..0b8d092 100644 --- a/src/pages/main/contents-item/StockContent.jsx +++ b/src/pages/main/contents-item/StockContent.jsx @@ -26,7 +26,7 @@ export default function StockContent(props) { const fetchData = async () => { try { const response = await axios.get( - `api/current-price?symbol=${companyCode}` + `/api/current-price?symbol=${companyCode}` ); setRatePerYesterday(response.data.output.prdy_ctrt); setsignPerYesterday(response.data.output.prdy_vrss_sign - 3); @@ -35,14 +35,17 @@ export default function StockContent(props) { } }; fetchData(); - }, []); + }, [props.companyCode]); return ( // !! div에 key값 붙여주세요. (redux 구현 후) {companyName} - +

({ratePerYesterday}%)

{signPerYesterday > 0 ? (

diff --git a/src/pages/stock/Stock.style.js b/src/pages/stock/Stock.style.js index a701d27..fc8ac47 100644 --- a/src/pages/stock/Stock.style.js +++ b/src/pages/stock/Stock.style.js @@ -8,6 +8,7 @@ export const StyledStockDiv = styled.div` export const StyledStockRightDiv = styled.div` background-color: ${(props) => (props.darkMode ? "#282828" : "white")}; + overflow-y: scroll; `; export const StyledStockHeaderDiv = styled.div`