diff --git a/src/components/popup/SuitableDetailPopup.tsx b/src/components/popup/SuitableDetailPopup.tsx index 6c41daf..7d45613 100644 --- a/src/components/popup/SuitableDetailPopup.tsx +++ b/src/components/popup/SuitableDetailPopup.tsx @@ -9,7 +9,7 @@ import { SUITABLE_HEAD_CODE, type Suitable, type SuitableDetail } from '@/types/ export default function SuitableDetailPopup() { const popupController = usePopupController() - const { toCodeName, toSuitableDetail, suitableCheckIcon, suitableCheckMemo, getSelectedSuitables } = useSuitable() + const { toCodeName, toSuitableDetail, suitableCheckIcon, suitableCheckMemo, getSelectedSuitables, suitableErrorAlert } = useSuitable() const [openItems, setOpenItems] = useState>(new Set()) const [selectedSuitables, setSelectedSuitables] = useState([]) @@ -24,9 +24,13 @@ export default function SuitableDetailPopup() { }, []) useEffect(() => { - getSelectedSuitables().then((res) => { - setSelectedSuitables(res) - }) + getSelectedSuitables() + .then((res) => { + setSelectedSuitables(res) + }) + .catch(() => { + suitableErrorAlert() + }) }, []) return ( diff --git a/src/components/suitable/SuitableButton.tsx b/src/components/suitable/SuitableButton.tsx index 3ce8a3c..ba2e7fa 100644 --- a/src/components/suitable/SuitableButton.tsx +++ b/src/components/suitable/SuitableButton.tsx @@ -6,12 +6,16 @@ import { useSuitableStore } from '@/store/useSuitableStore' export default function SuitableButton() { const popupController = usePopupController() - const { getSuitableIds, clearSuitableStore, downloadSuitablePdf } = useSuitable() + const { getSuitableIds, clearSuitableStore, downloadSuitablePdf, suitableErrorAlert } = useSuitable() const { selectedItems, addAllSelectedItem } = useSuitableStore() /* 데이터 전체 선택 */ const handleSelectAll = async () => { - addAllSelectedItem(await getSuitableIds()) + try { + addAllSelectedItem(await getSuitableIds()) + } catch (error) { + suitableErrorAlert() + } } /* 상세 팝업 열기 */ diff --git a/src/components/suitable/SuitableList.tsx b/src/components/suitable/SuitableList.tsx index 3e87f54..461beb9 100644 --- a/src/components/suitable/SuitableList.tsx +++ b/src/components/suitable/SuitableList.tsx @@ -19,7 +19,9 @@ export default function SuitableList() { hasNextPage, isFetchingNextPage, isLoading, + isError, suitableCheckIcon, + suitableErrorAlert, } = useSuitable() const { selectedItems, addSelectedItem, removeSelectedItem } = useSuitableStore() const [openItems, setOpenItems] = useState>(new Set()) @@ -144,6 +146,11 @@ export default function SuitableList() { useSpinnerStore.getState().setIsShow(isLoading || isFetchingNextPage) }, [isLoading, isFetchingNextPage]) + /* 조회 데이터 오류 처리 */ + useEffect(() => { + if (isError) suitableErrorAlert() + }, [isError]) + /* 조회 데이터 없는 경우 */ if (!suitableList.length) return diff --git a/src/hooks/useCommCode.ts b/src/hooks/useCommCode.ts index 6c78183..73f3918 100644 --- a/src/hooks/useCommCode.ts +++ b/src/hooks/useCommCode.ts @@ -9,7 +9,7 @@ export function useCommCode() { return response.data } catch (error) { console.error(`common code (${headCode}) load failed:`, error) - return [] + throw error } } diff --git a/src/hooks/useSuitable.ts b/src/hooks/useSuitable.ts index 4acb024..eb0b366 100644 --- a/src/hooks/useSuitable.ts +++ b/src/hooks/useSuitable.ts @@ -51,7 +51,7 @@ export function useSuitable() { return response.data } catch (error) { console.error(`지붕재 적합성 데이터 조회 실패: ${error}`) - return [] + throw error } } @@ -69,7 +69,7 @@ export function useSuitable() { return response.data } catch (error) { console.error(`지붕재 적합성 데이터 아이디 조회 실패: ${error}`) - return [] + throw error } } @@ -89,7 +89,7 @@ export function useSuitable() { return response.data } catch (error) { console.error(`지붕재 적합성 상세 데이터 조회 실패: ${error}`) - return [] + throw error } } @@ -101,9 +101,13 @@ export function useSuitable() { const getSuitableCommCode = (): void => { const headCodes = Object.values(SUITABLE_HEAD_CODE) as SUITABLE_HEAD_CODE[] for (const code of headCodes) { - getCommCode(code).then((res) => { - setSuitableCommCode(code, res) - }) + getCommCode(code) + .then((res) => { + setSuitableCommCode(code, res) + }) + .catch(() => { + suitableErrorAlert() + }) } } @@ -169,8 +173,8 @@ export function useSuitable() { hasNextPage, isFetchingNextPage, isLoading, - // isError, - // error, + isError, + error, } = useInfiniteQuery({ queryKey: ['suitables', 'list', searchCategory, searchKeyword], queryFn: async (context) => { @@ -189,6 +193,8 @@ export function useSuitable() { staleTime: 1000 * 60 * 10, gcTime: 1000 * 60 * 10, enabled: searchCategory !== '' || searchKeyword !== '', + retry: 1, + retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000), }) /** @@ -263,8 +269,12 @@ export function useSuitable() { * @returns {Promise} 선택된 지붕재 적합성 데이터 */ const getSelectedSuitables = async (): Promise => { - const { ids, detailIds } = serializeSelectedItems() - return await getSuitableDetails(ids, detailIds) + try { + const { ids, detailIds } = serializeSelectedItems() + return await getSuitableDetails(ids, detailIds) + } catch (error) { + throw error + } } /** @@ -308,11 +318,20 @@ export function useSuitable() { document.body.removeChild(form) } catch (error) { console.error(`지붕재 적합성 상세 데이터 pdf 다운로드 실패: ${error}`) + suitableErrorAlert() } } + /** + * @description 지붕재 적합성 오류 알림 표시 + * + * @returns {void} + */ + const suitableErrorAlert = (): void => { + alert('一時的なエラーが発生しました。 継続的な場合は、管理者に連絡してください。') + } + return { - getSuitables, getSuitableIds, getSuitableCommCode, toCodeName, @@ -323,10 +342,13 @@ export function useSuitable() { hasNextPage, isFetchingNextPage, isLoading, + isError, + error, getSelectedSuitables, clearSuitableStore, suitableCheckIcon, suitableCheckMemo, downloadSuitablePdf, + suitableErrorAlert, } } diff --git a/src/libs/logger.ts b/src/libs/logger.ts index d75083d..ab73450 100644 --- a/src/libs/logger.ts +++ b/src/libs/logger.ts @@ -2,6 +2,9 @@ import { NextRequest } from 'next/server' import { join } from 'path' import pino from 'pino' +/* 실행 모드 */ +const isProduction = process.env.NODE_ENV === 'production' + /* 로그 데이터 인터페이스 */ interface ApiLogData { responseStatus: number @@ -46,7 +49,7 @@ class DailyLogger { private createLogger(): pino.Logger { return pino( { - level: 'info', + level: isProduction ? 'info' : 'silent', timestamp: pino.stdTimeFunctions.isoTime, }, this.destination,