Skip to content

Commit 2073fcd

Browse files
committed
Merge branch 'dev' into release
2 parents c9e0482 + 70186c2 commit 2073fcd

8 files changed

Lines changed: 174 additions & 24 deletions

File tree

src/api/market.js

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,49 @@
11
const BASE = '/api/market'
22
const inFlightRequests = new Map()
33

4+
function tryParseJson(text) {
5+
try {
6+
return JSON.parse(text)
7+
} catch {
8+
return { message: text }
9+
}
10+
}
11+
12+
function resolveErrorMessage(data, fallbackMessage) {
13+
const candidates = [
14+
data?.message,
15+
data?.error,
16+
data?.detail,
17+
data?.details,
18+
data?.errorMessage,
19+
]
20+
21+
const resolved = candidates.find((value) => typeof value === 'string' && value.trim())
22+
return resolved?.trim() || fallbackMessage
23+
}
24+
25+
function createApiError(url, response, data, rawText, fallbackMessage) {
26+
const error = new Error(resolveErrorMessage(data, fallbackMessage))
27+
error.name = 'ApiError'
28+
error.status = response.status
29+
error.statusText = response.statusText
30+
error.url = url
31+
error.data = data
32+
error.rawText = rawText
33+
34+
const code = data?.code ?? data?.errorCode ?? data?.resultCode ?? data?.rt_cd
35+
if (code != null) {
36+
error.code = code
37+
}
38+
39+
return error
40+
}
41+
442
async function get(path, params, options = {}) {
5-
const query = params ? `?${new URLSearchParams(params).toString()}` : ''
43+
const filteredParams = params
44+
? Object.fromEntries(Object.entries(params).filter(([, value]) => value != null))
45+
: null
46+
const query = filteredParams ? `?${new URLSearchParams(filteredParams).toString()}` : ''
647
const url = `${BASE}${path}${query}`
748
const requestKey = `${options.method ?? 'GET'}:${url}`
849

@@ -17,18 +58,10 @@ async function get(path, params, options = {}) {
1758
})
1859

1960
const text = await res.text()
20-
let data = null
21-
22-
if (text) {
23-
try {
24-
data = JSON.parse(text)
25-
} catch {
26-
data = { message: text }
27-
}
28-
}
61+
const data = text ? tryParseJson(text) : null
2962

3063
if (!res.ok) {
31-
throw data ?? { message: '시장 데이터를 불러오지 못했습니다.' }
64+
throw createApiError(url, res, data, text, '시장 데이터를 불러오지 못했습니다.')
3265
}
3366

3467
return data

src/components/invest/InvestOrderBookPanel.jsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ export default function InvestOrderBookPanel({
77
currentPrice,
88
changeRate,
99
orderBook,
10+
isLoading = false,
11+
errorMessage = '',
1012
onSelectPrice,
1113
marketType,
1214
displayCurrency,
@@ -15,14 +17,29 @@ export default function InvestOrderBookPanel({
1517
const isUp = changeRate > 0
1618
const isDown = changeRate < 0
1719

20+
if (isLoading && !orderBook) {
21+
return (
22+
<section className="flex w-[170px] shrink-0 flex-col overflow-hidden border-r border-stroke bg-surface">
23+
<div className="flex items-center justify-between border-b border-stroke px-2.5 py-2 shrink-0">
24+
<span className="text-[11px] font-bold text-foreground">호가창</span>
25+
</div>
26+
<div className="flex flex-1 items-center justify-center px-3 py-4">
27+
<span className="text-[10px] text-foreground-disabled">호가 정보를 불러오는 중입니다.</span>
28+
</div>
29+
</section>
30+
)
31+
}
32+
1833
if (!orderBook) {
1934
return (
2035
<section className="flex w-[170px] shrink-0 flex-col overflow-hidden border-r border-stroke bg-surface">
2136
<div className="flex items-center justify-between border-b border-stroke px-2.5 py-2 shrink-0">
2237
<span className="text-[11px] font-bold text-foreground">호가창</span>
2338
</div>
2439
<div className="flex flex-1 items-center justify-center px-3 py-4">
25-
<span className="text-[10px] text-foreground-disabled">호가 정보를 불러올 수 없습니다.</span>
40+
<span className="text-[10px] text-center text-danger">
41+
{errorMessage || '호가 정보를 불러오지 못했습니다.'}
42+
</span>
2643
</div>
2744
</section>
2845
)

src/components/invest/InvestOrderSection.jsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ export default function InvestOrderSection({
2424
changeRate,
2525
defaultPrice,
2626
orderBook,
27+
orderBookLoading,
28+
orderBookErrorMessage,
2729
displayCurrency,
2830
usdRate,
2931
}) {
@@ -230,6 +232,8 @@ export default function InvestOrderSection({
230232
currentPrice={currentPrice}
231233
changeRate={changeRate}
232234
orderBook={orderBook}
235+
isLoading={orderBookLoading}
236+
errorMessage={orderBookErrorMessage}
233237
onSelectPrice={handleSelectPrice}
234238
marketType={marketType}
235239
displayCurrency={displayCurrency}

src/components/widgets/detail/StockChartDetail.jsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const WIDGET_PERIOD_MAP = {
1818
'3달': { initialPeriod: 'DAILY', initialMinuteInterval: undefined },
1919
}
2020

21-
export default function StockChartDetail({ config = {}, onClose }) {
21+
export default function StockChartDetail({ config = {} }) {
2222
const stockCode = config.stockCode ?? INVEST_STOCK.code
2323
const locationState = {
2424
stockName: config.stockName ?? null,
@@ -44,6 +44,8 @@ export default function StockChartDetail({ config = {}, onClose }) {
4444
hasMoreChartHistory,
4545
marketLoading,
4646
marketErrorMessage,
47+
orderBookLoading,
48+
orderBookErrorMessage,
4749
orderBook,
4850
onChartPeriodChange,
4951
onLoadMoreChartHistory,
@@ -114,6 +116,8 @@ export default function StockChartDetail({ config = {}, onClose }) {
114116
changeRate={changeRate}
115117
defaultPrice={currentPrice ?? stockMeta.price}
116118
orderBook={orderBook}
119+
orderBookLoading={orderBookLoading}
120+
orderBookErrorMessage={orderBookErrorMessage}
117121
displayCurrency={displayCurrency}
118122
usdRate={usdRate}
119123
/>

src/features/invest/domestic/useMarketData.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ const STALE = {
2525
finance: 1000 * 60 * 60,
2626
}
2727

28+
function resolveQueryErrorMessage(error, fallbackMessage) {
29+
const rawMessage = typeof error?.message === 'string' ? error.message.trim() : ''
30+
return rawMessage || fallbackMessage
31+
}
32+
2833
function mergeSeriesByTimestamp(...seriesGroups) {
2934
const merged = new Map()
3035

@@ -320,13 +325,22 @@ export default function useDomesticMarketData(stockCode, { enabled, activeDetail
320325
[chartHistorySeries, baseChartSeries],
321326
)
322327

328+
const marketError = priceQuery.error || dailyChartQuery.error || minuteChartQuery.error
329+
const marketErrorMessage = resolveQueryErrorMessage(marketError, '현재 시세를 불러오지 못하고 있습니다.')
330+
const dailyErrorMessage = resolveQueryErrorMessage(dailyChartQuery.error, '현재 일별 시세를 불러오지 못하고 있습니다.')
331+
const minuteErrorMessage = resolveQueryErrorMessage(minuteChartQuery.error, '현재 실시간 시세를 불러오지 못하고 있습니다.')
332+
const orderBookErrorMessage = resolveQueryErrorMessage(orderBookQuery.error, '현재 호가 정보를 불러오지 못하고 있습니다.')
333+
const customChartErrorMessage = resolveQueryErrorMessage(customChartQuery.error, '현재 차트 데이터를 불러오지 못하고 있습니다.')
334+
323335
const marketState = {
324336
isLoading: priceQuery.isLoading || dailyChartQuery.isLoading || minuteChartQuery.isLoading || orderBookQuery.isLoading,
325-
errorMessage: (priceQuery.error || dailyChartQuery.error || minuteChartQuery.error || orderBookQuery.error)?.message ?? '',
337+
errorMessage: marketError ? marketErrorMessage : '',
326338
dailyLoading: dailyChartQuery.isLoading,
327-
dailyErrorMessage: dailyChartQuery.error?.message ?? '',
339+
dailyErrorMessage: dailyChartQuery.error ? dailyErrorMessage : '',
328340
minuteLoading: minuteChartQuery.isLoading,
329-
minuteErrorMessage: minuteChartQuery.error?.message ?? '',
341+
minuteErrorMessage: minuteChartQuery.error ? minuteErrorMessage : '',
342+
orderBookLoading: orderBookQuery.isLoading,
343+
orderBookErrorMessage: orderBookQuery.error ? orderBookErrorMessage : '',
330344
priceData: livePrice ?? priceQuery.data ?? null,
331345
dailySeries: dailySeriesWithLive,
332346
minuteSeries: minuteSeriesWithLive,
@@ -335,7 +349,7 @@ export default function useDomesticMarketData(stockCode, { enabled, activeDetail
335349

336350
const chartState = {
337351
isLoading: customChartQuery.isLoading,
338-
errorMessage: customChartQuery.error?.message ?? '',
352+
errorMessage: customChartQuery.error ? customChartErrorMessage : '',
339353
series: customSeries,
340354
}
341355

src/features/invest/foreign/useMarketData.js

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useQuery } from '@tanstack/react-query'
2-
import { useEffect, useMemo, useState } from 'react'
2+
import { useEffect, useMemo, useRef, useState } from 'react'
33
import { foreignMarketApi } from '@/api/market'
44
import { DAY_MS, DEFAULT_MINUTE_INTERVAL } from '@/features/invest/constants'
55
import { formatApiDate } from '@/features/invest/formatters'
@@ -19,6 +19,52 @@ const STALE = {
1919
orderBook: 1000 * 5,
2020
}
2121

22+
function resolveQueryErrorMessage(error, fallbackMessage) {
23+
const rawMessage = typeof error?.message === 'string' ? error.message.trim() : ''
24+
25+
if (!rawMessage) return fallbackMessage
26+
if (rawMessage.startsWith('해외주식 API 호출 실패')) return fallbackMessage
27+
if (rawMessage === '시장 데이터를 불러오지 못했습니다.') return fallbackMessage
28+
29+
return rawMessage
30+
}
31+
32+
function useQueryErrorLogger(label, error, context) {
33+
const lastSignatureRef = useRef('')
34+
35+
useEffect(() => {
36+
if (!error) {
37+
lastSignatureRef.current = ''
38+
return
39+
}
40+
41+
const signature = [
42+
label,
43+
error.url ?? '',
44+
error.status ?? '',
45+
error.code ?? '',
46+
error.message ?? '',
47+
context.stockCode,
48+
context.exchcd,
49+
].join('|')
50+
51+
if (lastSignatureRef.current === signature) return
52+
lastSignatureRef.current = signature
53+
54+
console.error(`[foreign-market] ${label} query failed`, {
55+
...context,
56+
url: error.url ?? null,
57+
status: error.status ?? null,
58+
statusText: error.statusText ?? '',
59+
code: error.code ?? null,
60+
message: error.message ?? '',
61+
data: error.data ?? null,
62+
rawText: error.rawText ?? null,
63+
error,
64+
})
65+
}, [context, error, label])
66+
}
67+
2268
export default function useForeignMarketData(stockCode, exchcd, { enabled, initialPeriod, initialMinuteInterval }) {
2369
const [selectedChartPeriod, setSelectedChartPeriod] = useState(
2470
() => initialPeriod ?? localStorage.getItem('invest.chartPeriod') ?? 'MINUTE',
@@ -63,6 +109,16 @@ export default function useForeignMarketData(stockCode, exchcd, { enabled, initi
63109
staleTime: STALE.orderBook,
64110
})
65111

112+
const errorLogContext = useMemo(() => ({
113+
stockCode,
114+
exchcd,
115+
}), [exchcd, stockCode])
116+
117+
useQueryErrorLogger('price', priceQuery.error, errorLogContext)
118+
useQueryErrorLogger('daily-chart', dailyChartQuery.error, errorLogContext)
119+
useQueryErrorLogger('minute-chart', minuteChartQuery.error, errorLogContext)
120+
useQueryErrorLogger('order-book', orderBookQuery.error, errorLogContext)
121+
66122
const usesBaseMinuteData = selectedChartPeriod === 'MINUTE' && selectedMinuteInterval === DEFAULT_MINUTE_INTERVAL
67123
const needsCustomChart = enabled && !usesBaseMinuteData && selectedChartPeriod !== 'DAILY'
68124

@@ -87,20 +143,35 @@ export default function useForeignMarketData(stockCode, exchcd, { enabled, initi
87143
staleTime: selectedChartPeriod === 'MINUTE' ? STALE.minuteChart : STALE.dailyChart,
88144
})
89145

146+
useQueryErrorLogger('custom-chart', customChartQuery.error, {
147+
...errorLogContext,
148+
chartPeriod: selectedChartPeriod,
149+
minuteInterval: selectedMinuteInterval,
150+
})
151+
90152
const dailySeries = normalizeForeignDailySeries(dailyChartQuery.data?.dataPoints)
91153
const minuteSeries = normalizeForeignMinuteSeries(minuteChartQuery.data?.dataPoints)
92154

93155
const priceData = priceQuery.data
94156
? { currentPrice: priceQuery.data.price, changeAmount: priceQuery.data.diff, changeRate: priceQuery.data.rate }
95157
: null
96158

159+
const marketError = priceQuery.error || dailyChartQuery.error || minuteChartQuery.error
160+
const marketErrorMessage = resolveQueryErrorMessage(marketError, '현재 해외주식 시세를 불러오지 못하고 있습니다.')
161+
const dailyErrorMessage = resolveQueryErrorMessage(dailyChartQuery.error, '현재 해외주식 일별 시세를 불러오지 못하고 있습니다.')
162+
const minuteErrorMessage = resolveQueryErrorMessage(minuteChartQuery.error, '현재 해외주식 실시간 시세를 불러오지 못하고 있습니다.')
163+
const orderBookErrorMessage = resolveQueryErrorMessage(orderBookQuery.error, '현재 해외주식 호가 정보를 불러오지 못하고 있습니다.')
164+
const customChartErrorMessage = resolveQueryErrorMessage(customChartQuery.error, '현재 해외주식 차트 데이터를 불러오지 못하고 있습니다.')
165+
97166
const marketState = {
98167
isLoading: priceQuery.isLoading || dailyChartQuery.isLoading || minuteChartQuery.isLoading || orderBookQuery.isLoading,
99-
errorMessage: (priceQuery.error || dailyChartQuery.error || minuteChartQuery.error || orderBookQuery.error)?.message ?? '',
168+
errorMessage: marketError ? marketErrorMessage : '',
100169
dailyLoading: dailyChartQuery.isLoading,
101-
dailyErrorMessage: dailyChartQuery.error?.message ?? '',
170+
dailyErrorMessage: dailyChartQuery.error ? dailyErrorMessage : '',
102171
minuteLoading: minuteChartQuery.isLoading,
103-
minuteErrorMessage: minuteChartQuery.error?.message ?? '',
172+
minuteErrorMessage: minuteChartQuery.error ? minuteErrorMessage : '',
173+
orderBookLoading: orderBookQuery.isLoading,
174+
orderBookErrorMessage: orderBookQuery.error ? orderBookErrorMessage : '',
104175
priceData,
105176
dailySeries,
106177
minuteSeries,
@@ -117,7 +188,7 @@ export default function useForeignMarketData(stockCode, exchcd, { enabled, initi
117188

118189
const chartState = {
119190
isLoading: customChartQuery.isLoading,
120-
errorMessage: customChartQuery.error?.message ?? '',
191+
errorMessage: customChartQuery.error ? customChartErrorMessage : '',
121192
series: customSeries,
122193
}
123194

src/features/invest/useInvestMarketData.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export default function useInvestMarketData(stockCode, locationState, { activeLe
1313
const baseStockMeta = resolveStockMeta(stockCode, locationState)
1414
const { isDomestic } = baseStockMeta
1515
const exchcd = isDomestic ? null : getExchcd(baseStockMeta.exchangeCode)
16+
const locationStockName = locationState?.stockName
1617

1718
const infoQuery = useQuery({
1819
queryKey: isDomestic
@@ -27,7 +28,7 @@ export default function useInvestMarketData(stockCode, locationState, { activeLe
2728

2829
const stockMeta = useMemo(() => {
2930
if (isDomestic) {
30-
const resolvedName = locationState?.stockName
31+
const resolvedName = locationStockName
3132
?? infoQuery.data?.companyName
3233
?? infoQuery.data?.stockName
3334
?? infoQuery.data?.name
@@ -55,7 +56,7 @@ export default function useInvestMarketData(stockCode, locationState, { activeLe
5556
market: infoQuery.data?.exchangeName ?? baseStockMeta.market,
5657
sector: infoQuery.data?.induname ?? baseStockMeta.sector,
5758
}
58-
}, [baseStockMeta, infoQuery.data, isDomestic])
59+
}, [baseStockMeta, exchcd, infoQuery.data, isDomestic, locationStockName])
5960

6061
const domestic = useDomesticMarketData(stockCode, { enabled: isDomestic, activeDetailTab: activeLeftTab, initialPeriod, initialMinuteInterval })
6162
const foreign = useForeignMarketData(stockCode, exchcd, { enabled: !isDomestic, activeDetailTab: activeLeftTab, initialPeriod, initialMinuteInterval })
@@ -133,6 +134,8 @@ export default function useInvestMarketData(stockCode, locationState, { activeLe
133134
hasMoreChartHistory: active.hasMoreChartHistory ?? false,
134135
marketLoading: marketState.isLoading,
135136
marketErrorMessage: marketState.errorMessage,
137+
orderBookLoading: marketState.orderBookLoading ?? false,
138+
orderBookErrorMessage: marketState.orderBookErrorMessage ?? '',
136139
dailyRows: active.dailyRows,
137140
realtimeRows: active.realtimeRows,
138141
orderBook: active.orderBook,

src/pages/InvestPage.jsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ export default function InvestPage() {
4040
hasMoreChartHistory,
4141
marketLoading,
4242
marketErrorMessage,
43+
orderBookLoading,
44+
orderBookErrorMessage,
4345
dailyRows,
4446
realtimeRows,
4547
orderBook,
@@ -133,6 +135,8 @@ export default function InvestPage() {
133135
changeRate={changeRate}
134136
defaultPrice={currentPrice ?? stockMeta.price}
135137
orderBook={orderBook}
138+
orderBookLoading={orderBookLoading}
139+
orderBookErrorMessage={orderBookErrorMessage}
136140
displayCurrency={displayCurrency}
137141
usdRate={usdRate}
138142
/>

0 commit comments

Comments
 (0)