import { useInfiniteQuery } from '@tanstack/react-query' import { transformObjectKeys } from '@/libs/axios' import { useSuitableStore } from '@/store/useSuitableStore' import { useAxios } from './useAxios' import { useCommCode } from './useCommCode' import { SUITABLE_HEAD_CODE, type Suitable, type SuitableDetail, type SuitableIds } from '@/types/Suitable' export function useSuitable() { const { axiosInstance } = useAxios() const { getCommCode } = useCommCode() const { itemPerPage, suitableCommCode, setSuitableCommCode, searchCategory, clearSearchCategory, searchKeyword, clearSearchKeyword, selectedItems, clearSelectedItems, } = useSuitableStore() /** * @description 지붕재 적합성 데이터 조회. API를 호출하여 검색 조건에 맞는 지붕재 적합성 데이터를 페이지 단위로 조회 * * @param {Object} param * @param {number} [param.pageNumber] 페이지 번호 (기본값: 1) * @param {string} [param.category] 지붕재그룹코드 * @param {string} [param.keyword] 검색키워드 * * @returns {Promise} 지붕재 적합성 데이터 */ const getSuitables = async ({ pageNumber, category, keyword, }: { pageNumber?: number category?: string keyword?: string }): Promise => { try { const params: Record = { pageNumber: pageNumber || 1, itemPerPage: itemPerPage, } if (category) params.category = category if (keyword) params.keyword = keyword const response = await axiosInstance(null).get('/api/suitable/list', { params }) return response.data } catch (error) { console.error(`지붕재 적합성 데이터 조회 실패: ${error}`) throw error } } /** * @description 지붕재 적합성 데이터 아이디 조회. API를 호출하여 검색 조건에 맞는 지붕재 적합성 데이터의 main_id, detail_id를 조회 * * @returns {Promise} 지붕재 적합성 데이터 아이디 목록 */ const getSuitableIds = async (): Promise => { try { const params: Record = {} if (searchCategory) params.category = searchCategory if (searchKeyword) params.keyword = searchKeyword const response = await axiosInstance(null).get('/api/suitable/pick', { params }) return response.data } catch (error) { console.error(`지붕재 적합성 데이터 아이디 조회 실패: ${error}`) throw error } } /** * @description 지붕재 적합성 데이터 조회. API를 호출하여 지붕재 적합성 데이터의 모든 컬럼을 조회 * * @param {string} ids 지붕재 적합성 데이터의 main_id (쉼표로 구분된 문자열) * @param {string} [detailIds] 지붕재 적합성 데이터의 detail_id (쉼표로 구분된 문자열) * * @returns {Promise} 지붕재 적합성 데이터 */ const getSuitableDetails = async (ids: string, detailIds?: string): Promise => { try { const params: Record = { ids: ids } if (detailIds?.trim()) params.detailIds = detailIds const response = await axiosInstance(null).post('/api/suitable', params) return response.data } catch (error) { console.error(`지붕재 적합성 상세 데이터 조회 실패: ${error}`) throw error } } /** * @description 지붕재 적합성 공통 코드 조회. API를 호출하여 지붕재 적합성 데이터에서 사용하는 공통 코드 조회 후 스토어에 맵핑하여 저장 * * @returns {void} */ 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) }) .catch(() => { suitableErrorAlert() }) } } /** * @description 지붕재 적합성 공통 코드의 JP 코드명 조회 * * @param {string} headCode 공통 코드의 head_code * @param {string} code 공통 코드의 code * * @returns {string} 공통 코드의 JP 코드명 */ const toCodeName = (headCode: string, code: string): string => { const commCode = suitableCommCode.get(headCode) return commCode?.find((item) => item.code === code)?.codeJp || '' } /** * @description 지붕재 적합성 detail 데이터를 string 형태로 받아 파싱하여 반환 * * @param {string} suitableDetailString 지붕재 적합성 detail string * * @returns {SuitableDetail[]} 파싱된 지붕재 적합성 detail 데이터 */ const toSuitableDetail = (suitableDetailString: string): SuitableDetail[] => { if (!suitableDetailString) return [] try { const suitableDetailArray = transformObjectKeys(JSON.parse(suitableDetailString)) as SuitableDetail[] if (!Array.isArray(suitableDetailArray)) { throw new Error('suitableDetailArray is not an array') } return suitableDetailArray } catch (error) { console.error(`지붕재 적합성 detail 데이터 파싱 실패: ${error}`) return [] } } /** * @description 지붕재 적합성 상위 데이터 클릭 시 호출되는 함수. 지붕재 적합성 detail 데이터에서 id 목록을 추출하여 Set 형태로 반환 * * @param {string} suitableDetailString 지붕재 적합성 detail 데이터 * * @returns {Set} 지붕재 적합성 detail 데이터의 id 목록 */ const toSuitableDetailIds = (suitableDetailString: string): Set => { try { if (!suitableDetailString) return new Set() return new Set(JSON.parse(suitableDetailString).map(({ id }: { id: number }) => id)) } catch (error) { console.error(`지붕재 적합성 detail 데이터 파싱 실패: ${error}`) return new Set() } } /** * @description 지붕재 적합성 조회 페이지에서 보여지는 지붕재 적합성 데이터 조회 * * @returns {Object} 지붕재 적합성 데이터 조회 결과 */ const { data: suitables, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isError, error, } = useInfiniteQuery({ queryKey: ['suitables', 'list', searchCategory, searchKeyword], queryFn: async (context) => { const pageParam = context.pageParam as number if (pageParam === 1) clearSuitableStore({ items: true }) return await getSuitables({ pageNumber: pageParam, ...(searchCategory && { category: searchCategory }), ...(searchKeyword && { keyword: searchKeyword }), }) }, getNextPageParam: (lastPage: Suitable[], allPages: Suitable[][]) => { return lastPage.length === itemPerPage ? allPages.length + 1 : undefined }, initialPageParam: 1, staleTime: 1000 * 60 * 10, gcTime: 1000 * 60 * 10, enabled: searchCategory !== '' || searchKeyword !== '', retry: 1, retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000), }) /** * @description 지붕재 적합성 스토어 초기화. 선택된 지붕재 적합성 데이터, 검색 카테고리, 검색 키워드 초기화 * * @param {Object} param * @param {boolean} [param.items] 선택된 지붕재 적합성 데이터 초기화 여부 * @param {boolean} [param.category] 검색 카테고리 초기화 여부 * @param {boolean} [param.keyword] 검색 키워드 초기화 여부 * * @returns {void} */ const clearSuitableStore = ({ items = false, category = false, keyword = false }: { items?: boolean; category?: boolean; keyword?: boolean }) => { if (items) clearSelectedItems() if (category) clearSearchCategory() if (keyword) clearSearchKeyword() } /** * @description 지붕재 적합성 비고에 따른 아이콘 반환 * * @param {string} value 지붕재 적합성 비고 * * @returns {string} 지붕재 적합성 비고에 따른 아이콘 */ // TODO: 추후 지붕재 적합성 데이터 CUD 구현 시 ×, -, ー 데이터 관리 필요 const suitableCheckIcon = (value: string): string => { const iconMap: Record = { '×': '/assets/images/sub/compliance_x_icon.svg', '-': '/assets/images/sub/compliance_quest_icon.svg', 'ー': '/assets/images/sub/compliance_quest_icon.svg', default: '/assets/images/sub/compliance_check_icon.svg', } return iconMap[value] || iconMap.default } /** * @description 지붕재 적합성 비고에 따른 적합 결과 반환 * * @param {string} value 지붕재 적합성 비고 * * @returns {string} 지붕재 적합성 비고에 따른 적합 결과 */ // TODO: 추후 지붕재 적합성 데이터 CUD 구현 시 ○, ×, -, ー 데이터 관리 필요 const suitableCheckMemo = (value: string): string => { if (value === '○') return '設置可' if (value === '×') return '設置不可' if (value === '-' || value === 'ー') return 'お問い合わせください' return `${value}で設置可` } /** * @description 선택된 지붕재 적합성 데이터의 main_id, detail_id를 쉼표로 구분하여 반환. 선택된 지붕재 적합성 데이터가 없으면 빈 문자열 반환 * * @returns {Object} 선택된 지붕재 적합성 데이터의 main_id, detail_id * @returns {string} 선택된 지붕재 적합성 데이터의 main_id (쉼표로 구분된 문자열) * @returns {string} 선택된 지붕재 적합성 데이터의 detail_id (쉼표로 구분된 문자열) */ const serializeSelectedItems = (): { ids: string; detailIds: string } => { const ids: string[] = [] const detailIds: string[] = [] for (const [key, value] of selectedItems) { ids.push(String(key)) for (const id of value) detailIds.push(String(id)) } return { ids: ids.join(','), detailIds: detailIds.length > 0 ? detailIds.join(',') : '' } } /** * @description 선택된 지붕재 적합성 데이터 조회. API를 호출하여 선택된 지붕재 적합성 데이터의 모든 컬럼을 조회 * * @returns {Promise} 선택된 지붕재 적합성 데이터 */ const getSelectedSuitables = async (): Promise => { try { const { ids, detailIds } = serializeSelectedItems() return await getSuitableDetails(ids, detailIds) } catch (error) { throw error } } /** * @description 선택된 지붕재 적합성 데이터 pdf 다운로드. form 태그를 생성하여 직접 API를 호출하여 pdf 다운로드 * * @returns {Promise} 선택된 지붕재 적합성 데이터 pdf 다운로드 */ const downloadSuitablePdf = async (): Promise => { try { const { ids, detailIds } = serializeSelectedItems() const category = suitableCommCode.get(SUITABLE_HEAD_CODE.ROOF_MATL_GRP_CD)?.find((category) => category.code === searchCategory)?.codeJp const fileTitle = category ? `(${category}) 屋根材適合表` : '屋根材適合表' const form = document.createElement('form') form.method = 'POST' form.action = '/api/suitable/pdf' form.target = '_blank' const inputIds = document.createElement('input') inputIds.type = 'hidden' inputIds.name = 'ids' inputIds.value = ids const inputDetailIds = document.createElement('input') inputDetailIds.type = 'hidden' inputDetailIds.name = 'detailIds' inputDetailIds.value = detailIds const inputFileTitle = document.createElement('input') inputFileTitle.type = 'hidden' inputFileTitle.name = 'fileTitle' inputFileTitle.value = fileTitle form.appendChild(inputIds) if (detailIds) form.appendChild(inputDetailIds) form.appendChild(inputFileTitle) document.body.appendChild(form) form.submit() document.body.removeChild(form) } catch (error) { console.error(`지붕재 적합성 상세 데이터 pdf 다운로드 실패: ${error}`) suitableErrorAlert() } } /** * @description 지붕재 적합성 오류 알림 표시 * * @returns {void} */ const suitableErrorAlert = (): void => { alert('一時的なエラーが発生しました。 継続的な場合は、管理者に連絡してください。') } return { getSuitableIds, getSuitableCommCode, toCodeName, toSuitableDetail, toSuitableDetailIds, suitables, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isError, error, getSelectedSuitables, clearSuitableStore, suitableCheckIcon, suitableCheckMemo, downloadSuitablePdf, suitableErrorAlert, } }