Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/frontend/apps/web/app/stock/[stockSlug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default function StockDetailsPage({ params }) {
>
<ChevronLeft size={28} />
</Link>
<StockDetailLayout />
<StockDetailLayout stockSlug={stockSlug} />
</div>

<div className="pl-2 basis-[55%] flex-shrink-0 min-w-0">
Expand Down
22 changes: 12 additions & 10 deletions src/frontend/apps/web/src/features/stock/lib/stock-chart.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,25 @@ const formatChartData = <T>(
formatFn: (item: StockChartAPIResponse, time: Time) => T,
): T[] => {
return response
.map((item) => {
const formattedTime = formatTimeForChart(
item.businessDate,
item.tradingTime,
);
if (!formattedTime) return null;
return formatFn(item, formattedTime);
})
.filter((item): item is T => item !== null);
? response
.map((item) => {
const formattedTime = formatTimeForChart(
item.businessDate,
item.tradingTime,
);
if (!formattedTime) return null;
return formatFn(item, formattedTime);
})
.filter((item): item is T => item !== null)
: [];
};

export const formatCandleChart = (
response: StockChartAPIResponse[],
): CandleChart[] =>
formatChartData<CandleChart>(response, (item, time) => ({
time,
open: parseFloat(item.openPrice),
open: parseFloat(item.openPrice ?? item.openingPrice),
high: parseFloat(item.highPrice),
low: parseFloat(item.lowPrice),
close: parseFloat(item.currentPrice),
Expand Down
20 changes: 20 additions & 0 deletions src/frontend/apps/web/src/features/stock/model/chart.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Time } from 'lightweight-charts';

export enum ChartType {
Candlestick = 'candlestick',
Line = 'line',
Histogram = 'histogram',
}

export type CandleChart = {
time: Time;
open: number;
high: number;
low: number;
close: number;
};

export type DefaultChart = {
time: Time;
value: number;
};
10 changes: 3 additions & 7 deletions src/frontend/apps/web/src/features/stock/model/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
export {
type Stock,
type StockChartAPIResponse,
type CandleChart,
type DefaultChart,
ChartType,
} from './stock.types';
export type { StockTable, StockChartAPIResponse, StockWS } from './stock.types';
export { type CandleChart, type DefaultChart, ChartType } from './chart.types';
export { columns } from './stocks-table.columns';
export { dummyStockData, dummyRealTime } from './stock.mock';
export { useStockChart } from './use-stock-chart';
export { useStockWebSocket } from './use-stock-websocket';
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const STOCKS = {
'samsung-electronics': { code: '005930', name: '삼성전자' },
'sk-hynix': { code: '000660', name: 'SK하이닉스' },
kakao: { code: '035720', name: '카카오' },
naver: { code: '035420', name: 'NAVER' },
'hanwha-aerospace': { code: '012450', name: '한화에어로스페이스' },
} as const;
35 changes: 14 additions & 21 deletions src/frontend/apps/web/src/features/stock/model/stock.types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { Time } from 'lightweight-charts';

export type Stock = {
export type StockTable = {
id: string;
name: string;
currPrice: string;
Expand All @@ -13,30 +11,25 @@ export type StockChartAPIResponse = {
businessDate: string;
tradingTime: string;
currentPrice: string;
openPrice: string;
openPrice?: string;
openingPrice?: string;
highPrice: string;
lowPrice: string;
tradingVolume: string;
totalTradeAmount: string;
};

export enum ChartType {
Candlestick = 'candlestick',
Line = 'line',
Histogram = 'histogram',
}

export type CandleChart = {
time: Time;
open: number;
high: number;
low: number;
close: number;
};

export type DefaultChart = {
time: Time;
value: number;
export type StockWS = {
code: string;
htsKorIsnm: string;
stckBsopDate: string;
stckCntgHour: string;
stckPrpr: string;
stckOprc: string;
stckHgpr: string;
stckLwpr: string;
cntgVol: string;
acmlTrPbmn: string;
};

export type RealTimeStock = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
LineSeries,
HistogramSeries,
} from 'lightweight-charts';
import { CandleChart, ChartType, DefaultChart } from './stock.types';
import { CandleChart, ChartType, DefaultChart } from './chart.types';

export const useStockChart = (
chartType: ChartType,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { useCallback } from 'react';

import { useQueryClient } from '@tanstack/react-query';

import { useStompWebSocket } from '@/src/shared/providers';
import { QUERY_KEYS } from '@/src/shared/services';

export const useStockWebSocket = () => {
const queryClient = useQueryClient();
const { client, isConnected } = useStompWebSocket();

const subscribe = useCallback(() => {
if (!client) {
console.error(' WebSocket Client가 없습니다.');
return;
}

if (!isConnected) {
console.warn('WebSocket이 아직 연결되지 않았습니다. 구독을 대기합니다.');
return;
}

console.log(`Subscribing to /subscribe/stock`);
const subscription = client.subscribe(`/subscribe/stock`, (message) => {
try {
const payload = JSON.parse(message.body);
console.log('Received:', payload);

queryClient.setQueryData(
QUERY_KEYS.stock(payload.code),
(prev: any[] = []) => [...prev, payload],
);
} catch (error) {
console.error('메시지 파싱 실패:', error);
}
});

return () => {
subscription.unsubscribe();
};
}, [client, isConnected, queryClient]);

return { subscribe };
};
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
'use client';

import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from '@workspace/ui/components';
import StockChartItem from './stock-chart-item';
import { ChartType, dummyStockData } from '../model';
import { ChartType, StockChartAPIResponse, useStockWebSocket } from '../model';
import { useEffect } from 'react';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { QUERY_KEYS } from '@/src/shared/services';

const StockChartContainer = ({ stockCode }: { stockCode: string }) => {
const queryClient = useQueryClient();
const { subscribe } = useStockWebSocket();

useEffect(() => {
return subscribe();
}, [subscribe]);

const StockChartContainer = () => {
const data = dummyStockData;
const { data: stockData } = useQuery<StockChartAPIResponse[]>({
queryKey: QUERY_KEYS.stock(stockCode),
queryFn: () => queryClient.getQueryData(QUERY_KEYS.stock(stockCode)),
});
return (
<ResizablePanelGroup direction="vertical">
<ResizablePanel
Expand All @@ -17,7 +32,7 @@ const StockChartContainer = () => {
className="flex items-center justify-center bg-gray-200"
>
<StockChartItem
data={data}
data={stockData}
type={ChartType.Candlestick}
/>
</ResizablePanel>
Expand All @@ -29,7 +44,7 @@ const StockChartContainer = () => {
className="flex items-center justify-center bg-gray-300"
>
<StockChartItem
data={data}
data={stockData}
type={ChartType.Histogram}
/>
</ResizablePanel>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import { STOCKS } from '../model/stock.constants';
import StockChartContainer from './stock-chart-container';
import StockInfoContainer from './stock-info-container';

const StockDetailLayout = () => {
const StockDetailLayout = ({ stockSlug }: { stockSlug: string }) => {
const stockCode = STOCKS[stockSlug].code;
const stockName = STOCKS[stockSlug].name;

return (
<div className="w-full h-full flex flex-col min-w-0">
<div className="h-[120px]">
<StockInfoContainer />
<StockInfoContainer
stockCode={stockCode}
stockName={stockName}
/>
</div>
<div className="h-full rounded-md overflow-hidden">
<StockChartContainer />
<StockChartContainer stockCode={stockCode} />
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import StockInfo from './stock-info';
import StockLogo from './stock-logo';

const StockInfoContainer = () => {
const StockInfoContainer = ({
stockCode,
stockName,
}: {
stockCode: string;
stockName: string;
}) => {
return (
<div className="h-full flex flex-row justify-start items-center gap-3 min-w-0">
<StockLogo />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
useReactTable,
} from '@tanstack/react-table';
import { useState } from 'react';
import { columns } from '../model';
import { StockTable, columns } from '../model';
import {
Button,
DropdownMenu,
Expand Down
1 change: 1 addition & 0 deletions src/frontend/apps/web/src/shared/services/apis/querykey.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export const QUERY_KEYS = {
messages: (channelId: number) =>
['messages', `/subscribe/chat.${channelId}`] as const,
stock: (stockCode: string) => ['stock', stockCode] as const,
forwardHistory: (channelId: number) => ['forwardHistory', channelId] as const,
reverseHistory: (channelId: number) => ['reverseHistory', channelId] as const,
workspaceList: (workspaceId: number) =>
Expand Down