onsitesurvey/src/hooks/useSuitable.ts

261 lines
8.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useInfiniteQuery, useQuery } from '@tanstack/react-query'
import { SHA256 } from 'crypto-js'
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,
selectedCategory,
clearSelectedCategory,
searchKeyword,
clearSearchKeyword,
selectedItems,
clearSelectedItems,
selectedItemsSearching,
} = useSuitableStore()
const getSuitables = async ({
pageNumber,
category,
keyword,
}: {
pageNumber?: number
category?: string
keyword?: string
}): Promise<Suitable[]> => {
try {
const params: Record<string, string | number> = {
pageNumber: pageNumber || 1,
itemPerPage: itemPerPage,
}
if (category) params.category = category
if (keyword) params.keyword = keyword
const response = await axiosInstance(null).get<Suitable[]>('/api/suitable/list', { params })
return response.data
} catch (error) {
console.error('지붕재 데이터 로드 실패:', error)
return []
}
}
const getSuitableIds = async (): Promise<SuitableIds[]> => {
try {
const params: Record<string, string> = {}
if (selectedCategory) params.category = selectedCategory
if (searchKeyword) params.keyword = searchKeyword
const response = await axiosInstance(null).get<SuitableIds[]>('/api/suitable/pick', { params })
return response.data
} catch (error) {
console.error('지붕재 아이디 로드 실패:', error)
return []
}
}
const getSuitableDetails = async (ids: string, detailIds?: string): Promise<Suitable[]> => {
try {
const params: Record<string, string> = { ids: ids }
if (detailIds) params.detailIds = detailIds
const response = await axiosInstance(null).post<Suitable[]>('/api/suitable', params)
return response.data
} catch (error) {
console.error('지붕재 상세 데이터 로드 실패:', error)
return []
}
}
const getSuitableCommCode = () => {
const headCodes = Object.values(SUITABLE_HEAD_CODE) as SUITABLE_HEAD_CODE[]
for (const code of headCodes) {
getCommCode(code).then((res) => {
setSuitableCommCode(code, res)
})
}
}
const toCodeName = (headCode: string, code: string): string => {
const commCode = suitableCommCode.get(headCode)
return commCode?.find((item) => item.code === code)?.codeJp || ''
}
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('지붕재 데이터 파싱 실패:', error)
return []
}
}
const toSuitableDetailIds = (suitableDetailString: string): Set<number> => {
try {
return new Set<number>(JSON.parse(suitableDetailString).map(({ id }: { id: number }) => id))
} catch (error) {
console.error('지붕재 데이터 파싱 실패:', error)
return new Set()
}
}
const {
data: suitables,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
isLoading,
// isError,
// error,
} = useInfiniteQuery<Suitable[]>({
queryKey: ['suitables', 'list', selectedCategory, searchKeyword],
queryFn: async (context) => {
const pageParam = context.pageParam as number
if (pageParam === 1) clearSuitableStore({ items: true })
return await getSuitables({
pageNumber: pageParam,
...(selectedCategory && { category: selectedCategory }),
...(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: selectedCategory !== '' || searchKeyword !== '',
})
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(',') : '' }
}
const getSelectedItemsHash = (): string => {
const entries = Array.from(selectedItems.entries())
.map(([key, value]) => `${key}:${Array.from(value).sort().join(',')}`)
.sort()
.join('|')
return SHA256(entries).toString()
}
const {
data: selectedSuitables,
isLoading: isSelectedSuitablesLoading,
// refetch: refetchSelectedSuitables,
} = useQuery<Suitable[]>({
queryKey: ['suitables', 'selectedItems', getSelectedItemsHash(), selectedItemsSearching],
queryFn: async () => {
const { ids, detailIds } = serializeSelectedItems()
return await getSuitableDetails(ids, detailIds)
},
staleTime: Infinity,
gcTime: Infinity,
enabled: selectedItemsSearching,
refetchOnMount: false,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
})
const clearSuitableStore = ({ items = false, category = false, keyword = false }: { items?: boolean; category?: boolean; keyword?: boolean }) => {
if (items) clearSelectedItems()
if (category) clearSelectedCategory()
if (keyword) clearSearchKeyword()
}
// TODO: 추후 지붕재 적합성 데이터 CUD 구현 시 ×, -, ー 데이터 관리 필요
const suitableCheckIcon = (value: string): string => {
const iconMap: Record<string, string> = {
'×': '/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
}
// TODO: 추후 지붕재 적합성 데이터 CUD 구현 시 ○, ×, -, ー 데이터 관리 필요
const suitableCheckMemo = (value: string): string => {
if (value === '○') return '設置可'
if (value === '×') return '設置不可'
if (value === '-' || value === 'ー') return 'お問い合わせください'
return `${value}で設置可`
}
const downloadSuitablePdf = async (): Promise<void> => {
try {
const { ids, detailIds } = serializeSelectedItems()
const fileTitle = `(${
suitableCommCode.get(SUITABLE_HEAD_CODE.ROOF_MATL_GRP_CD)?.find((category) => category.code === selectedCategory)?.codeJp
}) 屋根材適合表`
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)
form.appendChild(inputDetailIds)
form.appendChild(inputFileTitle)
document.body.appendChild(form)
form.submit()
document.body.removeChild(form)
} catch (error) {
console.error('지붕재 상세 데이터 pdf 다운로드 실패:', error)
}
}
return {
getSuitables,
getSuitableIds,
getSuitableDetails,
getSuitableCommCode,
toCodeName,
toSuitableDetail,
toSuitableDetailIds,
suitables,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
isLoading,
selectedSuitables,
isSelectedSuitablesLoading,
clearSuitableStore,
suitableCheckIcon,
suitableCheckMemo,
downloadSuitablePdf,
}
}