onsitesurvey/src/hooks/useSurvey.ts

439 lines
14 KiB
TypeScript

import type { SubmitTargetResponse, SurveyBasicInfo, SurveyDetailRequest, SurveyRegistRequest } from '@/types/Survey'
import { useMemo } from 'react'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { useSurveyFilterStore } from '@/store/surveyFilterStore'
import { useSessionStore } from '@/store/session'
import { useAxios } from './useAxios'
import { queryStringFormatter } from '@/utils/common-utils'
import { useRouter } from 'next/navigation'
export const requiredFields = [
{
field: 'installationSystem',
name: '設置希望システム',
},
{
field: 'constructionYear',
name: '建築年数',
},
{
field: 'rafterSize',
name: '垂木サイズ',
},
{
field: 'rafterPitch',
name: '垂木傾斜',
},
{
field: 'waterproofMaterial',
name: '防水材',
},
{
field: 'insulationPresence',
name: '断熱材有無',
},
{
field: 'structureOrder',
name: '屋根構造の順序',
},
]
interface ZipCodeResponse {
status: number
message: string | null
results: ZipCode[] | null
}
type ZipCode = {
zipcode: string
prefcode: string
address1: string
address2: string
address3: string
kana1: string
kana2: string
kana3: string
}
/**
* @description 조사 매물 관련 기능을 제공하는 커스텀 훅
*
* @param {number} [id] 조사 매물 ID
* @param {boolean} [isPdf] PDF 뷰 여부
* @returns {Object} 조사 매물 관련 기능과 데이터
* @returns {SurveyBasicInfo[]} surveyList - 조사 매물 목록 데이터
* @returns {SurveyBasicInfo} surveyDetail - 조사 매물 상세 데이터
* @returns {boolean} isLoadingSurveyList - 조사 매물 목록 로딩 상태
* @returns {boolean} isLoadingSurveyDetail - 조사 매물 상세 데이터 로딩 상태
* @returns {boolean} isCreatingSurvey - 조사 매물 생성 중 상태
* @returns {boolean} isUpdatingSurvey - 조사 매물 수정 중 상태
* @returns {boolean} isDeletingSurvey - 조사 매물 삭제 중 상태
* @returns {boolean} isSubmittingSurvey - 조사 매물 제출 중 상태
* @returns {Function} createSurvey - 조사 매물 생성 함수
* @returns {Function} updateSurvey - 조사 매물 수정 함수
* @returns {Function} deleteSurvey - 조사 매물 삭제 함수
*/
export function useSurvey(
id?: number,
isPdf?: boolean,
): {
surveyList: { data: SurveyBasicInfo[]; count: number } | {}
surveyDetail: SurveyBasicInfo | null
isLoadingSurveyList: boolean
isLoadingSurveyDetail: boolean
isCreatingSurvey: boolean
isUpdatingSurvey: boolean
isDeletingSurvey: boolean
isSubmittingSurvey: boolean
createSurvey: (survey: SurveyRegistRequest) => Promise<number>
updateSurvey: ({ survey, isTemporary, storeId }: { survey: SurveyRegistRequest; isTemporary: boolean; storeId?: string | null }) => void
deleteSurvey: () => Promise<boolean>
submitSurvey: (params: { targetId?: string | null; targetNm?: string | null }) => void
validateSurveyDetail: (surveyDetail: SurveyDetailRequest) => string
getZipCode: (zipCode: string) => Promise<ZipCode[] | null>
refetchSurveyList: () => void
refetchSurveyDetail: () => void
getSubmitTarget: (params: { storeId: string; role: string }) => Promise<SubmitTargetResponse[] | null>
} {
const queryClient = useQueryClient()
const { keyword, searchOption, isMySurvey, sort, offset } = useSurveyFilterStore()
const { session } = useSessionStore()
const { axiosInstance } = useAxios()
const router = useRouter()
/**
* @description 조사 매물 목록, 상세 데이터 조회 에러 처리
*
* @param {any} error 에러 객체
* @returns {void} 라우팅 처리
*/
const errorRouter = (error: any) => {
const status = error.response?.status
if (error.response?.data.error) {
alert(error.response?.data.error)
}
switch (status) {
/** session 없는 경우 */
case 401:
router.replace('/login')
break
/** 조회 권한 없는 경우 */
case 403:
router.replace('/survey-sale')
break
/** 데이터 DB상 존재하지 않는 경우 */
case 404:
router.replace('/survey-sale')
break
/** 서버 오류 */
case 500:
router.back()
break
default:
break
}
}
/**
* @description 조사 매물 목록 조회
*
* @returns {Object} 조사 매물 목록 데이터
* @returns {SurveyBasicInfo[]} 조사 매물 목록 데이터
* @returns {number} 조건에 맞는 조사 매물 총 개수
* @returns {() => void} 조사 매물 목록 데이터 새로고침 함수
* @returns {boolean} 조사 매물 목록 로딩 상태
*/
const {
data: surveyListData,
isLoading: isLoadingSurveyList,
refetch: refetchSurveyList,
} = useQuery({
queryKey: ['survey', 'list', keyword, searchOption, isMySurvey, sort, offset, session?.storeNm, session?.builderId, session?.role],
queryFn: async () => {
try {
const resp = await axiosInstance(null).get<{ data: SurveyBasicInfo[]; count: number }>('/api/survey-sales', {
params: {
keyword,
searchOption,
isMySurvey,
sort,
offset,
storeId: session?.storeId,
builderId: session?.builderId,
role: session?.role,
},
})
return resp.data
} catch (error: any) {
errorRouter(error)
return { data: [], count: 0 }
}
},
})
/**
* @description 조사 매물 목록 데이터 메모이제이션
*
* @returns {Object} 메모이제이션된 조사 매물 목록 데이터
* @returns {number} count - 조건에 맞는 조사 매물 총 개수
* @returns {SurveyBasicInfo[]} data - 조사 매물 목록 데이터
*/
const surveyData = useMemo(() => {
if (!surveyListData) return { count: 0, data: [] }
return {
...surveyListData,
}
}, [surveyListData])
/**
* @description 조사 매물 상세 데이터 조회
*
* @returns {Object} 조사 매물 상세 데이터
* @returns {SurveyBasicInfo} surveyDetail - 조사 매물 상세 데이터
* @returns {boolean} isLoadingSurveyDetail - 조사 매물 상세 데이터 로딩 상태
* @returns {() => void} refetchSurveyDetail - 조사 매물 상세 데이터 새로고침 함수
*/
const {
data: surveyDetail,
isLoading: isLoadingSurveyDetail,
refetch: refetchSurveyDetail,
} = useQuery({
queryKey: ['survey', id],
queryFn: async () => {
if (Number.isNaN(id) || id === undefined || id === 0) return null
try {
const resp = await axiosInstance(null).get<SurveyBasicInfo>(`/api/survey-sales/${id}`, {
params: {
isPdf: isPdf,
},
})
return resp.data
} catch (error: any) {
errorRouter(error)
return null
}
},
enabled: id !== 0 && id !== undefined && id !== null,
})
/**
* @description 조사 매물 생성
*
* @param {SurveyRegistRequest} survey 생성할 조사 매물 데이터
* @returns {Promise<number>} 생성된 조사 매물 ID
*/
const { mutateAsync: createSurvey, isPending: isCreatingSurvey } = useMutation({
mutationFn: async (survey: SurveyRegistRequest) => {
const resp = await axiosInstance(null).post<{ id: number }>('/api/survey-sales', {
survey: survey,
storeId: session?.role === 'Partner' ? session?.builderId ?? null : session?.storeId ?? null,
role: session?.role ?? null,
})
return resp.data.id ?? 0
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['survey', 'list'] })
queryClient.invalidateQueries({ queryKey: ['survey', id] })
},
})
/**
* @description 조사 매물 수정
*
* @param {Object} params 수정할 데이터
* @param {SurveyRegistRequest} params.survey 수정할 조사 매물 데이터
* @param {boolean} params.isTemporary 임시 저장 여부
* @param {string|null} [params.storeId] 판매점 ID
* @returns {Promise<SurveyRegistRequest>} 수정된 조사 매물 데이터
* @throws {Error} id가 없는 경우 에러 발생
*/
const { mutate: updateSurvey, isPending: isUpdatingSurvey } = useMutation({
mutationFn: async ({ survey, isTemporary, storeId }: { survey: SurveyRegistRequest; isTemporary: boolean; storeId?: string | null }) => {
if (id === undefined) throw new Error('id is required')
const resp = await axiosInstance(null).put<SurveyRegistRequest>(`/api/survey-sales/${id}`, {
survey: survey,
isTemporary: isTemporary,
storeId: storeId,
role: session?.role ?? null,
})
return resp.data
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['survey', id] })
queryClient.invalidateQueries({ queryKey: ['survey', 'list'] })
},
onError: (error: any) => {
alert(error.response?.data.error)
},
})
/**
* @description 조사 매물 삭제
*
* @returns {Promise<boolean>} 삭제 성공 여부
* @throws {Error} id가 없는 경우 에러 발생
*
* @example
*
*
*/
const { mutateAsync: deleteSurvey, isPending: isDeletingSurvey } = useMutation({
mutationFn: async () => {
if (id === null) throw new Error('id is required')
const resp = await axiosInstance(null).delete<boolean>(`/api/survey-sales/${id}`)
return resp.data
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['survey', 'list'] })
},
onError: (error: any) => {
alert(error.response?.data.error)
},
})
/**
* @description 조사 매물 제출
*
* @param {Object} params 제출할 데이터
* @param {string|null} [params.targetId] 제출 대상 ID
* @param {string|null} [params.targetNm] 제출 대상 이름
* @returns {Promise<boolean>} 제출 성공 여부
* @throws {Error} id가 없는 경우 에러 발생
*/
const { mutateAsync: submitSurvey, isPending: isSubmittingSurvey } = useMutation({
mutationFn: async ({ targetId, targetNm }: { targetId?: string | null; targetNm?: string | null }) => {
if (!id) throw new Error('id is required')
const resp = await axiosInstance(null).patch<boolean>(`/api/survey-sales/${id}`, {
targetId,
targetNm,
})
return resp.data
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['survey', 'list'] })
queryClient.invalidateQueries({ queryKey: ['survey', id] })
},
onError: (error: any) => {
alert(error.response?.data.error)
},
})
/**
* @description 조사 매물 상세 데이터 유효성 검사
*
* @param {SurveyDetailRequest} surveyDetail 검사할 조사 매물 상세 데이터
* @returns {string} 빈 필드 이름 또는 빈 문자열
*/
const validateSurveyDetail = (surveyDetail: SurveyDetailRequest) => {
const ETC_FIELDS = ['installationSystem', 'rafterSize', 'rafterPitch', 'waterproofMaterial', 'structureOrder'] as const
const SPECIAL_CONDITIONS = ['constructionYear', 'insulationPresence'] as const
const isEmptyValue = (value: any): boolean => {
return value === null || value?.toString().trim() === ''
}
const checkRequiredField = (field: string): string => {
if (ETC_FIELDS.includes(field as (typeof ETC_FIELDS)[number])) {
if (
isEmptyValue(surveyDetail[field as keyof SurveyDetailRequest]) &&
isEmptyValue(surveyDetail[`${field}Etc` as keyof SurveyDetailRequest])
) {
return field
}
} else if (SPECIAL_CONDITIONS.includes(field as (typeof SPECIAL_CONDITIONS)[number])) {
if (surveyDetail[field as keyof SurveyDetailRequest] === '2' && isEmptyValue(surveyDetail[`${field}Etc` as keyof SurveyDetailRequest])) {
return `${field}Etc`
} else if (isEmptyValue(surveyDetail[field as keyof SurveyDetailRequest])) {
return field
}
}
return ''
}
// 필수 필드 체크
const emptyField = requiredFields.find((field) => checkRequiredField(field.field))
if (emptyField) return emptyField.field
// 계약 용량 단위 체크
const contractCapacity = surveyDetail.contractCapacity
if (contractCapacity?.trim() && contractCapacity.split(' ').length === 1) {
return 'contractCapacityUnit'
}
return ''
}
/**
* @description 우편번호 검색
*
* @param {string} zipCode 검색할 우편번호
* @returns {Promise<ZipCode[]|null>} 우편번호 검색 결과
* @throws {Error} 우편번호 검색 실패 시 에러 발생
*/
const getZipCode = async (zipCode: string): Promise<ZipCode[] | null> => {
try {
const { data } = await axiosInstance(null).get<ZipCodeResponse>(
`https://zipcloud.ibsnet.co.jp/api/search?${queryStringFormatter({ zipcode: zipCode.trim() })}`,
)
return data.results
} catch (error: any) {
console.error('Failed to fetch zipcode data:', error)
alert(error.response?.data.error)
throw new Error('Failed to fetch zipcode data')
}
}
/**
* @description 제출 대상 조회
*
* @param {Object} params 조회할 데이터
* @param {string} params.storeId 판매점 ID
* @param {string} params.role 사용자 권한
* @returns {Promise<SubmitTargetResponse[]|null>} 제출 대상 목록
*/
const getSubmitTarget = async (params: { storeId: string; role: string }): Promise<SubmitTargetResponse[] | null> => {
try {
if (!params.storeId) {
alert('販売店IDがありません。')
return null
}
const endpoints = {
Admin_Sub: `/api/submission/admin-sub?id=${params.storeId}`,
Builder: `/api/submission/builder?id=${params.storeId}`,
} as const
const endpoint = endpoints[params.role as keyof typeof endpoints]
if (!endpoint) {
alert('権限が間違っています。')
return null
}
const { data } = await axiosInstance(null).get<SubmitTargetResponse[]>(endpoint)
return data
} catch (error: any) {
alert(error.response?.data.error)
return null
}
}
return {
surveyList: surveyData.data,
surveyDetail: surveyDetail as SurveyBasicInfo | null,
isLoadingSurveyList,
isLoadingSurveyDetail,
isCreatingSurvey,
isUpdatingSurvey,
isDeletingSurvey,
isSubmittingSurvey,
createSurvey,
updateSurvey,
deleteSurvey,
submitSurvey,
validateSurveyDetail,
getZipCode,
getSubmitTarget,
refetchSurveyList,
refetchSurveyDetail,
}
}