feat: 조사매물 내 공통코드 동기화를 위한 surveyOptionStore 추가

This commit is contained in:
Daseul Kim 2025-08-05 17:21:43 +09:00
parent 7e2d93426f
commit b571197ffc
3 changed files with 267 additions and 16 deletions

View File

@ -1,7 +1,8 @@
import { useState } from 'react' import { useEffect, useState } from 'react'
import type { Mode, SurveyDetailInfo, SurveyDetailRequest } from '@/types/Survey' import type { Mode, SurveyDetailInfo, SurveyDetailRequest } from '@/types/Survey'
import { useAlertMsg, WARNING_MESSAGE } from '@/hooks/useAlertMsg' import { useAlertMsg, WARNING_MESSAGE } from '@/hooks/useAlertMsg'
import { radioEtcData, selectBoxOptions, supplementaryFacilities, roofMaterial } from '@/types/Survey' import { radioEtcData, supplementaryFacilities } from '@/types/Survey'
import { useSurveyOptionStore } from '@/store/surveyOptionStore'
const makeNumArr = (value: string) => { const makeNumArr = (value: string) => {
return value return value
@ -292,10 +293,15 @@ const SelectedBox = ({
detailInfoData: SurveyDetailInfo detailInfoData: SurveyDetailInfo
setRoofInfo: (roofInfo: SurveyDetailRequest) => void setRoofInfo: (roofInfo: SurveyDetailRequest) => void
}) => { }) => {
const { selectBoxOptions, initialized, loading, loadOptions } = useSurveyOptionStore()
const selectedId = detailInfoData?.[column as keyof SurveyDetailInfo] const selectedId = detailInfoData?.[column as keyof SurveyDetailInfo]
const etcValue = detailInfoData?.[`${column}Etc` as keyof SurveyDetailInfo] const etcValue = detailInfoData?.[`${column}Etc` as keyof SurveyDetailInfo]
const [isEtcSelected, setIsEtcSelected] = useState<boolean>(Boolean(etcValue)) const [isEtcSelected, setIsEtcSelected] = useState<boolean>(Boolean(etcValue))
useEffect(() => {
if (!initialized && !loading) loadOptions()
}, [initialized, loading])
const isSpecialCase = column === 'constructionYear' || column === 'installationAvailability' const isSpecialCase = column === 'constructionYear' || column === 'installationAvailability'
const showEtcOption = !isSpecialCase const showEtcOption = !isSpecialCase
@ -509,10 +515,15 @@ const MultiCheck = ({
setRoofInfo: (roofInfo: SurveyDetailRequest) => void setRoofInfo: (roofInfo: SurveyDetailRequest) => void
}) => { }) => {
const { showErrorAlert } = useAlertMsg() const { showErrorAlert } = useAlertMsg()
const { roofMaterial, initialized, loading, loadOptions } = useSurveyOptionStore()
const multiCheckData = column === 'supplementaryFacilities' ? supplementaryFacilities : roofMaterial const multiCheckData = column === 'supplementaryFacilities' ? supplementaryFacilities : roofMaterial
const etcValue = roofInfo?.[`${column}Etc` as keyof SurveyDetailInfo] const etcValue = roofInfo?.[`${column}Etc` as keyof SurveyDetailInfo]
const [isOtherCheck, setIsOtherCheck] = useState<boolean>(Boolean(etcValue)) const [isOtherCheck, setIsOtherCheck] = useState<boolean>(Boolean(etcValue))
useEffect(() => {
if (!initialized && !loading) loadOptions()
}, [initialized, loading])
const isRoofMaterial = column === 'roofMaterial' const isRoofMaterial = column === 'roofMaterial'
const selectedValues = makeNumArr(String(roofInfo[column as keyof SurveyDetailInfo] ?? '')) const selectedValues = makeNumArr(String(roofInfo[column as keyof SurveyDetailInfo] ?? ''))

View File

@ -0,0 +1,198 @@
import { create } from 'zustand'
import { axiosInstance } from '@/libs/axios'
import { selectBoxOptions as defaultSelectBoxOptions, roofMaterial as defaultRoofMaterial, type SelectBoxKeys } from '@/types/Survey'
import { type CommCode } from '@/types/CommCode'
export const SURVEY_OPTION_HEAD_ID: { [key: string]: string } = {
/* 지붕재 종류 : HEAD_ID = 'ROOF_MATL' (HEAD_CD = 114200) */
roofMaterial: 'ROOF_MATL',
/* 건축 연수 : HEAD_ID = 'BUILDING' (HEAD_CD = 114000) */
constructionYear: 'BUILDING',
/* 서까래 피치 : HEAD_ID = 'RAFT_BASE_CD' (HEAD_CD = 203800) */
rafterPitch: 'RAFT_BASE_CD',
/* 골목판 종류 : HEAD_ID = 'ROOF_BOARD' (HEAD_CD = 114600) */
openFieldPlateKind: 'ROOF_BOARD',
}
/*
* 2025-08-04
*
* 목록 : SURVEY_OPTION_HEAD_ID -> roofMaterial, constructionYear, rafterPitch, openFieldPlateKind
* 제약사항 : BC_COMM_L.CODE code값 . null .
* 설명 : 공통코드 code값을 code_jp(name) .
* code가 null인 name값 (= , )
*
* comm_cd : {
* // 지붕재 종류 : HEAD_ID = 'ROOF_MATL' (HEAD_CD = 114200)
* roofMaterial : [
* // 슬레이트
* {
* head_cd: '114200',
* code: '7',
* code_jp: 'スレート',
* },
* // 아스팔트 싱글
* {
* head_cd: '114200',
* code: '8',
* code_jp: 'アスファルトシングル',
* },
* // 기와
* {
* head_cd: '114200',
* code: null,
* code_jp: '瓦',
* },
* // 금속지붕
* {
* head_cd: '114200',
* code: null,
* code_jp: '金属屋根',
* },
* ],
* // 건축 연수 : HEAD_ID = 'BUILDING' (HEAD_CD = 114000)
* constructionYear : [
* // 신축
* {
* head_cd: '114000',
* code: 'N',
* code_jp: '新築',
* },
* // 기축
* {
* head_cd: '114000',
* code: 'O',
* code_jp: '既築',
* },
* ],
* // 서까래 피치 : HEAD_ID = 'RAFT_BASE_CD' (HEAD_CD = 203800)
* rafterPitch : [
* // 455mm 이하
* {
* head_cd: '203800',
* code: 'HEI_455',
* code_jp: '455mm以下',
* },
* // 500mm 이하
* {
* head_cd: '203800',
* code: 'HEI_500',
* code_jp: '500mm以下',
* },
* // 606mm 이하
* {
* head_cd: '203800',
* code: 'HEI_606',
* code_jp: '606mm以下',
* },
* ],
* // 골목판 종류 : HEAD_ID = 'ROOF_BOARD' (HEAD_CD = 114600)
* openFieldPlateKind : [
* // 구조용합판
* {
* head_cd: '114600',
* code: null,
* code_jp: '構造用合板',
* },
* // OSB
* {
* head_cd: '114600',
* code: 'O',
* code_jp: 'OSB',
* },
* // 파티클보드
* {
* head_cd: '114600',
* code: 'A',
* code_jp: 'パーティクルボード',
* },
* // 소판
* {
* head_cd: '114600',
* code: 'S',
* code_jp: '小幅板',
* },
* ]
* }
*/
interface SurveyOptionState {
/* 지붕재종류 데이터 */
roofMaterial: typeof defaultRoofMaterial
/* 셀렉트박스 옵션 데이터 */
selectBoxOptions: typeof defaultSelectBoxOptions
/* 로딩 여부 */
loading: boolean
/* 옵션 로드 완료 여부 */
initialized: boolean
/* 옵션 로드 */
loadOptions: () => Promise<void>
}
export const useSurveyOptionStore = create<SurveyOptionState>((set, get) => ({
roofMaterial: defaultRoofMaterial,
selectBoxOptions: defaultSelectBoxOptions,
loading: false,
initialized: false,
loadOptions: async () => {
if (get().initialized || get().loading) return
set({ loading: true })
try {
const promises = Object.entries(SURVEY_OPTION_HEAD_ID).map(async ([optionKey, headId]) => {
try {
const response = await axiosInstance(process.env.NEXT_PUBLIC_API_URL).get<CommCode[]>('/api/comm-code', {
params: { headId: headId },
})
const commCodeData = response.data
return { optionKey, commCodeData }
} catch (error) {
console.error(`${optionKey} 로드 실패:`, error)
return { optionKey, commCodeData: [] }
}
})
const results: { optionKey: string; commCodeData: CommCode[] }[] = await Promise.all(promises)
set((prev) => {
const newState = {
roofMaterial: [...prev.roofMaterial],
selectBoxOptions: { ...prev.selectBoxOptions },
}
results.forEach(({ optionKey, commCodeData }) => {
if (optionKey === 'roofMaterial') {
// 지붕재 종류 업데이트
newState.roofMaterial = newState.roofMaterial.map((item) => {
if (item.code) {
const commCode = commCodeData.find((c) => c.code === item.code)
if (commCode) {
return { ...item, code: commCode.code, name: commCode.codeJp }
}
}
return item
})
} else {
// 셀렉트박스 옵션 업데이트
const key = optionKey as SelectBoxKeys
newState.selectBoxOptions[key] = newState.selectBoxOptions[key].map((item) => {
if (item.code) {
const commCode = commCodeData.find((c) => c.code === item.code)
if (commCode) {
return { ...item, code: commCode.code, name: commCode.codeJp }
}
}
return item
})
}
})
return { ...newState, initialized: true, loading: false }
})
} catch (error) {
console.error('옵션 데이터 로드 중 오류 발생:', error)
set({ loading: false })
}
},
}))

View File

@ -332,7 +332,7 @@ type RadioEtcKeys =
| 'insulationPresence' | 'insulationPresence'
| 'rafterDirection' | 'rafterDirection'
| 'leakTrace' | 'leakTrace'
type SelectBoxKeys = export type SelectBoxKeys =
| 'installationSystem' | 'installationSystem'
| 'constructionYear' | 'constructionYear'
| 'roofShape' | 'roofShape'
@ -341,155 +341,187 @@ type SelectBoxKeys =
| 'openFieldPlateKind' | 'openFieldPlateKind'
| 'installationAvailability' | 'installationAvailability'
export const supplementaryFacilities = [ export const supplementaryFacilities: { id: number; code: string | null; name: string }[] = [
/** 에코큐트 */ /** 에코큐트 */
{ id: 1, name: 'エコキュート' }, { id: 1, code: null, name: 'エコキュート' },
/** 에네팜 */ /** 에네팜 */
{ id: 2, name: 'エネパーム' }, { id: 2, code: null, name: 'エネパーム' },
/** 축전지시스템 */ /** 축전지시스템 */
{ id: 3, name: '蓄電池システム' }, { id: 3, code: null, name: '蓄電池システム' },
/** 태양광발전 */ /** 태양광발전 */
{ id: 4, name: '太陽光発電' }, { id: 4, code: null, name: '太陽光発電' },
] ]
export const roofMaterial = [ // 지붕재 종류 : HEAD_ID = 'ROOF_MATL' (114200)
export const roofMaterial: { id: number; code: string | null; name: string }[] = [
/** 슬레이트 */ /** 슬레이트 */
{ id: 1, name: 'スレート' }, { id: 1, code: '7', name: 'スレート' },
/** 아스팔트 싱글 */ /** 아스팔트 싱글 */
{ id: 2, name: 'アスファルトシングル' }, { id: 2, code: '8', name: 'アスファルトシングル' },
/** 기와 */ /** 기와 */
{ id: 3, name: '瓦' }, { id: 3, code: null, name: '瓦' },
/** 금속지붕 */ /** 금속지붕 */
{ id: 4, name: '金属屋根' }, { id: 4, code: null, name: '金属屋根' },
] ]
export const selectBoxOptions: Record<SelectBoxKeys, { id: number; name: string }[]> = { export const selectBoxOptions: Record<SelectBoxKeys, { id: number; code: string | null; name: string }[]> = {
installationSystem: [ installationSystem: [
{ {
/** 태양광발전 */ /** 태양광발전 */
id: 1, id: 1,
code: null,
name: '太陽光発電', name: '太陽光発電',
}, },
{ {
/** 하이브리드축전지시스템 */ /** 하이브리드축전지시스템 */
id: 2, id: 2,
code: null,
name: 'ハイブリッド蓄電システム', name: 'ハイブリッド蓄電システム',
}, },
{ {
/** 축전지시스템 */ /** 축전지시스템 */
id: 3, id: 3,
code: null,
name: '蓄電池システム', name: '蓄電池システム',
}, },
], ],
// 건축 연수 : HEAD_ID = 'BUILDING' (114000)
constructionYear: [ constructionYear: [
{ {
/** 신축 */ /** 신축 */
id: 1, id: 1,
code: 'N',
name: '新築', name: '新築',
}, },
{ {
/** 기축 */ /** 기축 */
id: 2, id: 2,
code: 'O',
name: '既築', name: '既築',
}, },
], ],
roofShape: [ roofShape: [
{ {
/** 박공지붕 */ /** 박공지붕 */
id: 1, id: 1,
code: null,
name: '切妻', name: '切妻',
}, },
{ {
/** 기동 */ /** 기동 */
id: 2, id: 2,
code: null,
name: '寄棟', name: '寄棟',
}, },
{ {
/** 한쪽흐름 */ /** 한쪽흐름 */
id: 3, id: 3,
code: null,
name: '片流れ', name: '片流れ',
}, },
], ],
rafterSize: [ rafterSize: [
{ {
/** 35mm 이상×48mm 이상 */ /** 35mm 이상×48mm 이상 */
id: 1, id: 1,
code: null,
name: '幅35mm以上×高さ48mm以上', name: '幅35mm以上×高さ48mm以上',
}, },
{ {
/** 36mm 이상×46mm 이상 */ /** 36mm 이상×46mm 이상 */
id: 2, id: 2,
code: null,
name: '幅36mm以上×高さ46mm以上', name: '幅36mm以上×高さ46mm以上',
}, },
{ {
/** 37mm 이상×43mm 이상 */ /** 37mm 이상×43mm 이상 */
id: 3, id: 3,
code: null,
name: '幅37mm以上×高さ43mm以上', name: '幅37mm以上×高さ43mm以上',
}, },
{ {
/** 38mm 이상×40mm 이상 */ /** 38mm 이상×40mm 이상 */
id: 4, id: 4,
code: null,
name: '幅38mm以上×高さ40mm以上', name: '幅38mm以上×高さ40mm以上',
}, },
], ],
// 서까래 피치 : HEAD_ID = 'RAFT_BASE_CD' (203800)
rafterPitch: [ rafterPitch: [
{ {
/** 455mm 이하 */ /** 455mm 이하 */
id: 1, id: 1,
code: 'HEI_455',
name: '455mm以下', name: '455mm以下',
}, },
{ {
/** 500mm 이하 */ /** 500mm 이하 */
id: 2, id: 2,
code: 'HEI_500',
name: '500mm以下', name: '500mm以下',
}, },
{ {
/** 606mm 이하 */ /** 606mm 이하 */
id: 3, id: 3,
code: 'HEI_606',
name: '606mm以下', name: '606mm以下',
}, },
], ],
// 골목판 종류 : HEAD_ID = 'ROOF_BOARD' (114600)
openFieldPlateKind: [ openFieldPlateKind: [
{ {
/** 구조용합판 */ /** 구조용합판 */
id: 1, id: 1,
code: null,
name: '構造用合板', name: '構造用合板',
}, },
{ {
/** OSB */ /** OSB */
id: 2, id: 2,
code: 'O',
name: 'OSB', name: 'OSB',
}, },
{ {
/** 파티클보드 */ /** 파티클보드 */
id: 3, id: 3,
code: 'A',
name: 'パーティクルボード', name: 'パーティクルボード',
}, },
{ {
/** 소판 */ /** 소판 */
id: 4, id: 4,
code: 'S',
name: '小幅板', name: '小幅板',
}, },
], ],
installationAvailability: [ installationAvailability: [
{ {
/** 확인완료 */ /** 확인완료 */
id: 1, id: 1,
code: null,
name: '確認済み', name: '確認済み',
}, },
{ {
/** 미확인 */ /** 미확인 */
id: 2, id: 2,
code: null,
name: '未確認', name: '未確認',
}, },
], ],
} }
export const radioEtcData: Record<RadioEtcKeys, { id: number; label: string }[]> = { export const radioEtcData: Record<RadioEtcKeys, { id: number; code: string | null; label: string }[]> = {
structureOrder: [ structureOrder: [
{ {
/** 지붕재 - 방수재 - 지붕의기초 - 서까래 */ /** 지붕재 - 방수재 - 지붕의기초 - 서까래 */
id: 1, id: 1,
code: null,
label: '屋根材 > 防水材 > 屋根の基礎 > 垂木', label: '屋根材 > 防水材 > 屋根の基礎 > 垂木',
}, },
], ],
@ -497,6 +529,7 @@ export const radioEtcData: Record<RadioEtcKeys, { id: number; label: string }[]>
{ {
/** 목재 */ /** 목재 */
id: 1, id: 1,
code: null,
label: '木製', label: '木製',
}, },
], ],
@ -504,11 +537,13 @@ export const radioEtcData: Record<RadioEtcKeys, { id: number; label: string }[]>
{ {
/** 목재 */ /** 목재 */
id: 1, id: 1,
code: null,
label: '木製', label: '木製',
}, },
{ {
/** 강재 */ /** 강재 */
id: 2, id: 2,
code: null,
label: '強制', label: '強制',
}, },
], ],
@ -516,6 +551,7 @@ export const radioEtcData: Record<RadioEtcKeys, { id: number; label: string }[]>
{ {
/** 아스팔트 지붕 940(22kg 이상) */ /** 아스팔트 지붕 940(22kg 이상) */
id: 1, id: 1,
code: null,
label: 'アスファルト屋根94022kg以上', label: 'アスファルト屋根94022kg以上',
}, },
], ],
@ -523,11 +559,13 @@ export const radioEtcData: Record<RadioEtcKeys, { id: number; label: string }[]>
{ {
/** 없음 */ /** 없음 */
id: 1, id: 1,
code: null,
label: 'なし', label: 'なし',
}, },
{ {
/** 있음 */ /** 있음 */
id: 2, id: 2,
code: null,
label: 'あり', label: 'あり',
}, },
], ],
@ -535,11 +573,13 @@ export const radioEtcData: Record<RadioEtcKeys, { id: number; label: string }[]>
{ {
/** 수직 */ /** 수직 */
id: 1, id: 1,
code: null,
label: '垂直垂木', label: '垂直垂木',
}, },
{ {
/** 수평 */ /** 수평 */
id: 2, id: 2,
code: null,
label: '水平垂木', label: '水平垂木',
}, },
], ],
@ -547,11 +587,13 @@ export const radioEtcData: Record<RadioEtcKeys, { id: number; label: string }[]>
{ {
/** 있음 */ /** 있음 */
id: 1, id: 1,
code: null,
label: 'あり', label: 'あり',
}, },
{ {
/** 없음 */ /** 없음 */
id: 2, id: 2,
code: null,
label: 'なし', label: 'なし',
}, },
], ],