Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FE] 차트 고도화 & 뉴스 API 연동 & 거래현황 소켓 연결 #220

Merged
merged 7 commits into from
Nov 27, 2024
20 changes: 10 additions & 10 deletions FE/src/components/News/Card.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
import { NewsMockDataType } from './newsMockData.ts';
import { NewsDataType } from './NewsDataType.ts';
import { formatDate } from '../../utils/formatTime.ts';

type CardWithImageProps = {
data: NewsMockDataType;
data: NewsDataType;
};
export default function Card({ data }: CardWithImageProps) {
return (
<a
className='flex cursor-pointer flex-col rounded-lg border p-4 transition-all hover:bg-juga-grayscale-50'
href={data.link}
href={data.originallink}
target='_blank'
rel='noopener noreferrer'
>
<div className={'mb-2 flex w-full flex-row items-center justify-between'}>
<div className={'flex flex-row items-center gap-3'}>
<span className='rounded-full bg-juga-blue-10 px-2 py-0.5 text-xs text-juga-blue-50'>
증권
</span>
<h3 className='w-[320px] truncate text-left text-base font-medium'>
{data.title}
</h3>
</div>
<span className={'w-fit text-sm text-gray-500'}>{data.date}</span>
<span className={'w-fit text-sm text-gray-500'}>
{formatDate(data.pubDate)}
</span>
</div>
<div className='flex w-full items-center justify-between gap-4'>
<p className='w-96 truncate text-left text-sm text-juga-grayscale-500'>
{data.img}
{data.description}
</p>
<span className='whitespace-nowrap text-sm text-juga-grayscale-500'>
{data.publisher}
<span className='rounded-full bg-juga-blue-10 px-2 py-0.5 text-xs text-juga-blue-50'>
{data.query}
</span>
</div>
</a>
Expand Down
23 changes: 19 additions & 4 deletions FE/src/components/News/News.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,32 @@
import Card from './Card.tsx';
import { newsMockData } from './newsMockData.ts';
import { useQuery } from '@tanstack/react-query';
import { getNewsData } from '../../service/getNewsData.ts';
import { NewsDataType } from './NewsDataType.ts';

export default function News() {
const { data, isLoading, isError } = useQuery({
queryKey: ['News'],
queryFn: () => getNewsData(),
staleTime: 1000 * 60,
});

if (isError) return <div>Error!!</div>;
if (isLoading) return <div>Loading...</div>;

const randomNewsIndex = Math.floor(Math.random() * 16);

return (
<div className='w-full'>
<div className='mb-4 flex items-center justify-between'>
<h2 className='text-xl font-bold'>주요 뉴스</h2>
</div>

<div className='grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-2'>
{newsMockData.slice(0, 4).map((news, index) => (
<Card key={index} data={news} />
))}
{data.news
.slice(randomNewsIndex, randomNewsIndex + 4)
.map((news: NewsDataType, index: number) => (
<Card key={index} data={news} />
))}
</div>
</div>
);
Expand Down
7 changes: 7 additions & 0 deletions FE/src/components/News/NewsDataType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export type NewsDataType = {
title: string;
description: string;
pubDate: string;
originallink: string;
query: string;
};
4 changes: 3 additions & 1 deletion FE/src/components/Search/SearchCardHighlight.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { formatNoSpecialChar } from '../../utils/formatNoSpecialChar.ts';

type SearchCardHighLightProps = {
text: string;
highlight: string;
Expand All @@ -11,7 +13,7 @@ export const SearchCardHighLight = ({
return <div>{text}</div>;
}

const targetWord = highlight.trim();
const targetWord = formatNoSpecialChar(highlight.trim());

const parts = text.trim().split(new RegExp(`(${targetWord})`, 'gi'));
return (
Expand Down
6 changes: 3 additions & 3 deletions FE/src/components/Search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import Lottie from 'lottie-react';
import searchAnimation from 'assets/searchAnimation.json';
import { useSearchHistory } from './searchHistoryHook.ts';
import { formatNoSpecialChar } from '../../utils/formatNoSpecialChar.ts';

export default function SearchModal() {
const { isOpen, toggleSearchModal } = useSearchModalStore();
Expand All @@ -23,20 +24,19 @@
shouldSearch ? searchInput : '',
500,
);

const { data, isLoading, isFetching } = useQuery({
queryKey: ['search', debounceValue],
queryFn: () => getSearchResults(debounceValue),
queryFn: () => getSearchResults(formatNoSpecialChar(debounceValue)),
enabled: !!debounceValue && !isDebouncing,
staleTime: 1000,
cacheTime: 1000 * 60,
});

useEffect(() => {
if (data && data.length > 0 && debounceValue && !isLoading) {
addSearchHistory(debounceValue);
addSearchHistory(formatNoSpecialChar(debounceValue));
}
}, [data, debounceValue]);

Check warning on line 39 in FE/src/components/Search/index.tsx

View workflow job for this annotation

GitHub Actions / FE-test-and-build

React Hook useEffect has missing dependencies: 'addSearchHistory' and 'isLoading'. Either include them or remove the dependency array

if (!isOpen) return null;

Expand Down
7 changes: 2 additions & 5 deletions FE/src/components/StocksDetail/Chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import { drawUpperYAxis } from 'utils/chart/drawUpperYAxis.ts';
import { drawLowerYAxis } from 'utils/chart/drawLowerYAxis.ts';
import { drawChartGrid } from 'utils/chart/drawChartGrid.ts';
import { drawMouseGrid } from '../../utils/chart/drawMouseGrid.ts';
import { drawMouseGrid } from 'utils/chart/drawMouseGrid.ts';

const categories: { label: string; value: TiemCategory }[] = [
{ label: '일', value: 'D' },
Expand Down Expand Up @@ -46,7 +46,6 @@
const upperChartY = useRef<HTMLCanvasElement>(null);
const lowerChartY = useRef<HTMLCanvasElement>(null);
const chartX = useRef<HTMLCanvasElement>(null);
// RAF 관리를 위한 ref
const rafRef = useRef<number>();
const [timeCategory, setTimeCategory] = useState<TiemCategory>('D');
const [charSizeConfig, setChartSizeConfig] = useState<ChartSizeConfigType>({
Expand All @@ -64,14 +63,12 @@
y: 0,
});


const { data, isLoading } = useQuery(
['stocksChartData', code, timeCategory],
() => getStocksChartDataByCode(code, timeCategory),
{ staleTime: 1000 },
);


const handleMouseDown = useCallback((e: MouseEvent) => {
e.preventDefault();
setIsDragging(true);
Expand Down Expand Up @@ -285,7 +282,7 @@
);
}
},
[

Check warning on line 285 in FE/src/components/StocksDetail/Chart.tsx

View workflow job for this annotation

GitHub Actions / FE-test-and-build

React Hook useCallback has unnecessary dependencies: 'drawBarChart', 'drawCandleChart', 'drawChartGrid', 'drawLineChart', 'drawLowerYAxis', 'drawUpperYAxis', 'drawXAxis', and 'padding'. Either exclude them or remove the dependency array. Outer scope values like 'padding' aren't valid dependencies because mutating them doesn't re-render the component
padding,
upperLabelNum,
lowerLabelNum,
Expand Down Expand Up @@ -361,7 +358,7 @@

return (
<div className='box-border flex h-[260px] flex-col items-center rounded-lg bg-white p-3'>
<div className='flex items-center justify-between w-full h-fit'>
<div className='flex h-fit w-full items-center justify-between'>
<p className='font-semibold'>차트</p>
<nav className='flex gap-4 text-sm'>
{categories.map(({ label, value }) => (
Expand Down
5 changes: 2 additions & 3 deletions FE/src/components/StocksDetail/PriceSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,20 @@
},
);
},
[id, buttonFlag],

Check warning on line 34 in FE/src/components/StocksDetail/PriceSection.tsx

View workflow job for this annotation

GitHub Actions / FE-test-and-build

React Hook useCallback has a missing dependency: 'queryClient'. Either include it or remove the dependency array
);

useEffect(() => {
// 이벤트 리스너 등록
if (!buttonFlag) return;
const handleTradeHistory = (chartData: PriceDataType) => {
addData(chartData);
};

socket.on(`trade-history/${id}`, handleTradeHistory);

return () => {
socket.off(`trade-history/${id}`, handleTradeHistory);
};
}, [id, addData]);
}, [id, addData, buttonFlag]);

useEffect(() => {
const tmpIndex = buttonFlag ? 0 : 1;
Expand Down
4 changes: 2 additions & 2 deletions FE/src/components/StocksDetail/PriceTableColumn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ export default function PriceTableColumn({ viewMode }: Props) {
return (
<thead className={'z-1 sticky top-0 bg-white'}>
<tr className={'h-10 border-b text-gray-500'}>
<th className={'px-4 py-1 text-left font-medium'}>채결가</th>
<th className={'px-4 py-1 text-right font-medium'}>채결량(주)</th>
<th className={'px-4 py-1 text-left font-medium'}>체결가</th>
<th className={'px-4 py-1 text-right font-medium'}>체결량(주)</th>
<th className={'px-4 py-1 text-right font-medium'}>등락률</th>
{/*<th className={'px-4 py-1 text-right font-medium'}>거래량(주)</th>*/}
<th className={'px-4 py-1 text-right font-medium'}>시간</th>
Expand Down
7 changes: 7 additions & 0 deletions FE/src/service/getNewsData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const getNewsData = async () => {
const response = await fetch(`${import.meta.env.VITE_API_URL}/news`);
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
};
2 changes: 2 additions & 0 deletions FE/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export type StockChartUnit = {
stck_lwpr: string;
acml_vol: string;
prdy_vrss_sign: string;
mov_avg_5: string;
mov_avg_20: string;
};

export type MypageSectionType = 'account' | 'order' | 'info';
Expand Down
9 changes: 8 additions & 1 deletion FE/src/utils/chart/drawCandleChart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@ export function drawCandleChart(
const n = data.length;

const values = data
.map((d) => [+d.stck_hgpr, +d.stck_lwpr, +d.stck_clpr, +d.stck_oprc])
.map((d) => [
+d.stck_hgpr,
+d.stck_lwpr,
+d.stck_clpr,
+d.stck_oprc,
Math.floor(+d.mov_avg_5),
Math.floor(+d.mov_avg_20),
])
.flat();
const yMax = Math.round(Math.max(...values) * (1 + weight));
const yMin = Math.round(Math.min(...values) * (1 - weight));
Expand Down
28 changes: 18 additions & 10 deletions FE/src/utils/chart/drawLineChart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,43 @@ export function drawLineChart(
height: number,
padding: Padding,
weight: number = 0,
lineWidth: number = 1,
lineWidth: number = 4,
) {
if (data.length === 0) return;

ctx.beginPath();

const n = data.length;
const yMax = Math.round(
Math.max(...data.map((d: StockChartUnit) => +d.stck_oprc)) * (1 + weight),
);
const yMin = Math.round(
Math.min(...data.map((d: StockChartUnit) => +d.stck_oprc)) * (1 - weight),
);
const gap = Math.floor(width / n);

const values = data
.map((d) => [
+d.stck_hgpr,
+d.stck_lwpr,
+d.stck_clpr,
+d.stck_oprc,
Math.floor(+d.mov_avg_5),
Math.floor(+d.mov_avg_20),
])
.flat();
const yMax = Math.round(Math.max(...values) * (1 + weight));
const yMin = Math.round(Math.min(...values) * (1 - weight));

data.forEach((e, i) => {
const cx = x + padding.left + (width * i) / (n - 1);
const cx = x + padding.left + (width * i) / (n - 1) + gap / 2;
const cy =
y +
padding.top +
height -
(height * (+e.stck_oprc - yMin)) / (yMax - yMin);
(height * (+e.mov_avg_5 - yMin)) / (yMax - yMin);

if (i === 0) {
ctx.moveTo(cx, cy);
} else {
ctx.lineTo(cx, cy);
}
});

ctx.strokeStyle = '#000';
ctx.lineWidth = lineWidth;
ctx.stroke();
}
9 changes: 8 additions & 1 deletion FE/src/utils/chart/drawUpperYAxis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,14 @@ export const drawUpperYAxis = (
upperChartHeight: number,
) => {
const values = data
.map((d) => [+d.stck_hgpr, +d.stck_lwpr, +d.stck_clpr, +d.stck_oprc])
.map((d) => [
+d.stck_hgpr,
+d.stck_lwpr,
+d.stck_clpr,
+d.stck_oprc,
Math.floor(+d.mov_avg_5),
Math.floor(+d.mov_avg_20),
])
.flat();
const yMax = Math.round(Math.max(...values) * (1 + weight));
const yMin = Math.round(Math.min(...values) * (1 - weight));
Expand Down
3 changes: 3 additions & 0 deletions FE/src/utils/formatNoSpecialChar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const formatNoSpecialChar = (query: string) => {
return query.replace(/[^a-zA-Z0-9가-힣 ]|\\/g, '');
};
5 changes: 5 additions & 0 deletions FE/src/utils/formatTime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,8 @@ export function formatTime(time: string) {
const day = time.slice(6, 8);
return `${year}.${mon}.${day}`;
}

export function formatDate(dateString: string) {
const date = new Date(dateString);
return `${date.getFullYear()}.${date.getMonth() + 1}.${date.getDate()} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
}
3 changes: 2 additions & 1 deletion FE/src/utils/useDebounce.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useEffect, useState } from 'react';
import { formatNoSpecialChar } from './formatNoSpecialChar.ts';

export const useDebounce = (value: string, delay: number) => {
const [debounceValue, setDebounceValue] = useState(value);
Expand All @@ -8,7 +9,7 @@ export const useDebounce = (value: string, delay: number) => {
setIsDebouncing(true);

const handler = setTimeout(() => {
setDebounceValue(value);
setDebounceValue(formatNoSpecialChar(value));
setIsDebouncing(false);
}, delay);

Expand Down