feature/common-code : 문의 및 조사매물 제출 공통코드 조회 로직 수정 #128

Merged
seul merged 6 commits from feature/common-code into dev 2025-08-07 14:54:06 +09:00
9 changed files with 76 additions and 123 deletions

View File

@ -1,15 +1,14 @@
import { NextRequest, NextResponse } from 'next/server' import { NextRequest, NextResponse } from 'next/server'
import { loggerWrapper } from '@/libs/api-wrapper' import { loggerWrapper } from '@/libs/api-wrapper'
import { prisma } from '@/libs/prisma' import { prisma } from '@/libs/prisma'
import { CommonCode } from '@/types/Inquiry' import { CommCode } from '@/types/CommCode'
async function getCommCode(request: NextRequest): Promise<NextResponse> { async function getCommCode(request: NextRequest): Promise<NextResponse> {
try { try {
const searchParams = request.nextUrl.searchParams const searchParams = request.nextUrl.searchParams
const headId = searchParams.get('headId') const headId = searchParams.get('headId')
if (headId === 'QNA_CD') {
return getQnaCd() if (!headId) return NextResponse.json({ error: '필수 파라미터가 없습니다' }, { status: 400 })
}
// @ts-ignore // @ts-ignore
const commHeadData = await prisma.BC_COMM_H.findFirst({ const commHeadData = await prisma.BC_COMM_H.findFirst({
@ -21,15 +20,10 @@ async function getCommCode(request: NextRequest): Promise<NextResponse> {
}, },
}) })
if (!commHeadData) { if (!commHeadData) return NextResponse.json({ error: `${headId}를 찾을 수 없습니다` }, { status: 404 })
return NextResponse.json({ error: `${headId}를 찾을 수 없습니다` }, { status: 404 })
}
if (headId === 'SALES_OFFICE_CD') {
return getSaleOffice(commHeadData.HEAD_CD)
}
// @ts-ignore // @ts-ignore
const roofMaterials: CommCode[] = await prisma.BC_COMM_L.findMany({ const results: CommCode[] = await prisma.BC_COMM_L.findMany({
where: { where: {
HEAD_CD: commHeadData.HEAD_CD, HEAD_CD: commHeadData.HEAD_CD,
}, },
@ -37,79 +31,17 @@ async function getCommCode(request: NextRequest): Promise<NextResponse> {
HEAD_CD: true, HEAD_CD: true,
CODE: true, CODE: true,
CODE_JP: true, CODE_JP: true,
REF_CHR1: true,
REF_NUM1: true,
}, },
orderBy: { orderBy: {
CODE: 'asc', CODE: 'asc',
}, },
}) })
return NextResponse.json(roofMaterials) return NextResponse.json(results)
} catch (error) { } catch (error) {
console.error('❌ 데이터 조회 중 오류가 발생했습니다:', error) console.error('❌ 데이터 조회 중 오류가 발생했습니다:', error)
return NextResponse.json({ error: '데이터 조회 중 오류가 발생했습니다' }, { status: 500 }) return NextResponse.json({ error: '데이터 조회 중 오류가 발생했습니다' }, { status: 500 })
} }
} }
const getSaleOffice = async (headCode: string) => {
// @ts-ignore
const commCodeSaleOffice: CommCode[] = await prisma.BC_COMM_L.findMany({
where: {
HEAD_CD: headCode,
REF_NUM1: 1,
},
select: {
CODE: true,
CODE_JP: true,
REF_CHR1: true,
REF_NUM1: true,
},
})
return NextResponse.json(commCodeSaleOffice)
}
/**
* @description QNA
* @returns {CommonCode[]} QNA
*/
const getQnaCd = async () => {
// @ts-ignore
const headCdList: { HEAD_CD: string; HEAD_ID: string }[] = await prisma.BC_COMM_H.findMany({
where: {
OR: [{ HEAD_ID: { in: ['QNA_CLS_LRG_CD'] } }, { HEAD_ID: { in: ['QNA_CLS_MID_CD'] } }, { HEAD_ID: { in: ['QNA_CLS_SML_CD'] } }],
},
select: {
HEAD_CD: true,
HEAD_ID: true,
},
})
const result: CommonCode[] = []
// @ts-ignore
const commCodeQna: CommCode[] = await prisma.BC_COMM_L.findMany({
where: {
HEAD_CD: {
in: headCdList.map((item) => item.HEAD_CD).filter(Boolean),
},
},
select: {
HEAD_CD: true,
CODE: true,
CODE_JP: true,
REF_CHR1: true,
},
})
commCodeQna.forEach((item) => {
result.push({
// @ts-ignore
headCd: item.HEAD_CD,
// @ts-ignore
headId: headCdList.find((headCd) => headCd.HEAD_CD === item.HEAD_CD)?.HEAD_ID ?? '',
// @ts-ignore
code: item.CODE,
// @ts-ignore
name: item.CODE_JP,
// @ts-ignore
refChar1: item.REF_CHR1 ?? '',
})
})
return NextResponse.json(result)
}
export const GET = loggerWrapper(getCommCode) export const GET = loggerWrapper(getCommCode)

View File

@ -2,7 +2,7 @@
import { useInquiry } from '@/hooks/useInquiry' import { useInquiry } from '@/hooks/useInquiry'
import { useSessionStore } from '@/store/session' import { useSessionStore } from '@/store/session'
import { InquiryRequest } from '@/types/Inquiry' import { InquiryCommCode, InquiryRequest } from '@/types/Inquiry'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { CONFIRM_MESSAGE, SUCCESS_MESSAGE, useAlertMsg, WARNING_MESSAGE } from '@/hooks/useAlertMsg' import { CONFIRM_MESSAGE, SUCCESS_MESSAGE, useAlertMsg, WARNING_MESSAGE } from '@/hooks/useAlertMsg'
@ -152,15 +152,15 @@ export default function RegistForm() {
</option> </option>
{commonCodeList {commonCodeList
.filter((code) => code.headId === 'QNA_CLS_LRG_CD') .filter((code) => code.headCd === InquiryCommCode.QNA_CLS_LRG_CD.headCd)
.map((code) => ( .map((code) => (
<option key={code.code} value={code.code}> <option key={code.code} value={code.code}>
{code.name} {code.codeJp}
</option> </option>
))} ))}
</select> </select>
</div> </div>
{commonCodeList.filter((code) => code.refChar1 === inquiryRequest.qnaClsLrgCd).length > 0 && inquiryRequest.qnaClsLrgCd && ( {commonCodeList.filter((code) => code.refChr1 === inquiryRequest.qnaClsLrgCd).length > 0 && inquiryRequest.qnaClsLrgCd && (
<div className="data-input mt5"> <div className="data-input mt5">
<select <select
className="select-form" className="select-form"
@ -173,16 +173,16 @@ export default function RegistForm() {
</option> </option>
{commonCodeList {commonCodeList
.filter((code) => code.refChar1 === inquiryRequest.qnaClsLrgCd) .filter((code) => code.refChr1 === inquiryRequest.qnaClsLrgCd)
.map((code) => ( .map((code) => (
<option key={code.code} value={code.code}> <option key={code.code} value={code.code}>
{code.name} {code.codeJp}
</option> </option>
))} ))}
</select> </select>
</div> </div>
)} )}
{commonCodeList.filter((code) => code.refChar1 === inquiryRequest.qnaClsMidCd).length > 0 && inquiryRequest.qnaClsMidCd && ( {commonCodeList.filter((code) => code.refChr1 === inquiryRequest.qnaClsMidCd).length > 0 && inquiryRequest.qnaClsMidCd && (
<div className="data-input mt5"> <div className="data-input mt5">
<select <select
className="select-form" className="select-form"
@ -195,10 +195,10 @@ export default function RegistForm() {
</option> </option>
{commonCodeList {commonCodeList
.filter((code) => code.refChar1 === inquiryRequest.qnaClsMidCd) .filter((code) => code.refChr1 === inquiryRequest.qnaClsMidCd)
.map((code) => ( .map((code) => (
<option key={code.code} value={code.code}> <option key={code.code} value={code.code}>
{code.name} {code.codeJp}
</option> </option>
))} ))}
</select> </select>

View File

@ -63,7 +63,8 @@ export default function SurveySaleSubmitPopup() {
/** Admin 제출 폼 데이터 삽입 - 1차 판매점*/ /** Admin 제출 폼 데이터 삽입 - 1차 판매점*/
if (session?.role === 'Admin') { if (session?.role === 'Admin') {
getCommCode('SALES_OFFICE_CD').then((codes) => { getCommCode('SALES_OFFICE_CD').then((codes) => {
setCommCodeList(codes) const filteredCodes = codes.filter((code) => code.refNum1 === '1')
setCommCodeList(filteredCodes)
}) })
setSubmitData((prev) => ({ setSubmitData((prev) => ({
...prev, ...prev,
@ -221,8 +222,7 @@ export default function SurveySaleSubmitPopup() {
onChange={(e) => { onChange={(e) => {
const selectedOffice = commCodeList.find((item) => item.code === e.target.value) const selectedOffice = commCodeList.find((item) => item.code === e.target.value)
if (selectedOffice) { if (selectedOffice) {
//@ts-ignore const receiver = selectedOffice.refChr1?.split(';') || []
const receiver = selectedOffice.REF_CHR1.split(';')
setSubmitData((prev) => ({ setSubmitData((prev) => ({
...prev, ...prev,
receiver: receiver, receiver: receiver,

View File

@ -97,12 +97,10 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) {
/** 저장 로직 */ /** 저장 로직 */
const handleSave = (isTemporary: boolean) => { const handleSave = (isTemporary: boolean) => {
const emptyField = validateSurveyDetail(data.roof)
if (isTemporary) { if (isTemporary) {
tempSaveProcess() tempSaveProcess()
} else { } else {
saveProcess(emptyField) saveProcess()
} }
} }
@ -134,7 +132,9 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) {
} }
/** 저장 로직 */ /** 저장 로직 */
const saveProcess = async (emptyField: string | null) => { const saveProcess = async () => {
const emptyField = validateSurveyDetail(data.roof)
if (emptyField?.trim() === '') { if (emptyField?.trim() === '') {
showConfirm(CONFIRM_MESSAGE.SAVE_CONFIRM, async () => { showConfirm(CONFIRM_MESSAGE.SAVE_CONFIRM, async () => {
await handleSuccessfulSave() await handleSuccessfulSave()

View File

@ -91,15 +91,13 @@ export function useAxios() {
// Handle uppercase snake_case (e.g., USER_NAME -> userName) // Handle uppercase snake_case (e.g., USER_NAME -> userName)
// Handle lowercase snake_case (e.g., user_name -> userName) // Handle lowercase snake_case (e.g., user_name -> userName)
if (/^[A-Z_]+$/.test(key) || /^[a-z_]+$/.test(key)) { if (/^[A-Z0-9_]+$/.test(key) || /^[a-z0-9_]+$/.test(key)) {
transformedKey = snakeToCamel(key) transformedKey = snakeToCamel(key)
} }
// Handle single uppercase word (e.g., ROLE -> role) // Handle single uppercase word (e.g., ROLE -> role)
else if (/^[A-Z]+$/.test(key)) { else if (/^[A-Z0-9]+$/.test(key)) {
transformedKey = key.toLowerCase() transformedKey = key.toLowerCase()
} }
// Preserve existing camelCase
acc[transformedKey] = transformObjectKeys(obj[key]) acc[transformedKey] = transformObjectKeys(obj[key])
return acc return acc
}, {}) }, {})
@ -109,7 +107,7 @@ export function useAxios() {
} }
const snakeToCamel = (str: string): string => { const snakeToCamel = (str: string): string => {
return str.toLowerCase().replace(/([-_][a-z])/g, (group) => group.toUpperCase().replace('-', '').replace('_', '')) return str.toLowerCase().replace(/_([a-z])/g, (match, letter) => letter.toUpperCase())
} }
return { return {

View File

@ -1,11 +1,12 @@
import { InquiryList, Inquiry, InquirySaveResponse, CommonCode } from '@/types/Inquiry' import { InquiryList, Inquiry, InquirySaveResponse, InquiryCommCode } from '@/types/Inquiry'
import { useAxios } from '@/hooks/useAxios' import { useAxios } from '@/hooks/useAxios'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { useInquiryFilterStore } from '@/store/inquiryFilterStore' import { useInquiryFilterStore } from '@/store/inquiryFilterStore'
import { useMemo } from 'react' import { useMemo } from 'react'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { useAlertMsg } from '@/hooks/useAlertMsg' import { useAlertMsg } from '@/hooks/useAlertMsg'
import { useCommCode } from './useCommCode'
import { CommCode } from '@/types/CommCode'
/** /**
* @description * @description
@ -20,7 +21,7 @@ import { useAlertMsg } from '@/hooks/useAlertMsg'
* @returns {boolean} isSavingInquiry - * @returns {boolean} isSavingInquiry -
* @returns {Function} saveInquiry - * @returns {Function} saveInquiry -
* @returns {Function} downloadFile - * @returns {Function} downloadFile -
* @returns {CommonCode[]} commonCodeList - * @returns {CommCode[]} commonCodeList -
*/ */
export function useInquiry( export function useInquiry(
qnoNo?: number, qnoNo?: number,
@ -33,7 +34,7 @@ export function useInquiry(
isSavingInquiry: boolean isSavingInquiry: boolean
saveInquiry: (formData: FormData) => Promise<InquirySaveResponse> saveInquiry: (formData: FormData) => Promise<InquirySaveResponse>
downloadFile: (encodeFileNo: number, srcFileNm: string) => Promise<Blob | null> downloadFile: (encodeFileNo: number, srcFileNm: string) => Promise<Blob | null>
commonCodeList: CommonCode[] commonCodeList: CommCode[]
} { } {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const { inquiryListRequest, offset, isMyInquiry } = useInquiryFilterStore() const { inquiryListRequest, offset, isMyInquiry } = useInquiryFilterStore()
@ -216,7 +217,8 @@ export function useInquiry(
} }
/** /**
* @description * @description
* - , ,
* *
* @returns {Object} * @returns {Object}
* @returns {CommonCode[]} data - * @returns {CommonCode[]} data -
@ -225,15 +227,13 @@ export function useInquiry(
const { data: commonCodeList, isLoading: isLoadingCommonCodeList } = useQuery({ const { data: commonCodeList, isLoading: isLoadingCommonCodeList } = useQuery({
queryKey: ['commonCodeList'], queryKey: ['commonCodeList'],
queryFn: async () => { queryFn: async () => {
const isListQuery = false const { getCommCode } = useCommCode()
const shouldThrowError = false const resp = await Promise.all([
getCommCode(InquiryCommCode.QNA_CLS_LRG_CD.headId),
const resp = await tryFunction( getCommCode(InquiryCommCode.QNA_CLS_MID_CD.headId),
() => axiosInstance(null).get<CommonCode[]>('/api/comm-code', { params: { headId: 'QNA_CD' } }), getCommCode(InquiryCommCode.QNA_CLS_SML_CD.headId),
isListQuery, ])
shouldThrowError, return resp.flat()
)
return resp?.data ?? []
}, },
staleTime: Infinity, staleTime: Infinity,
gcTime: Infinity, gcTime: Infinity,

View File

@ -1,4 +1,11 @@
import type { SubmitTargetResponse, SurveyBasicInfo, SurveyDetailRequest, SurveyRegistRequest } from '@/types/Survey' import {
radioEtcData,
selectBoxOptions,
type SubmitTargetResponse,
type SurveyBasicInfo,
type SurveyDetailRequest,
type SurveyRegistRequest,
} from '@/types/Survey'
import { useMemo } from 'react' import { useMemo } from 'react'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { useSurveyFilterStore } from '@/store/surveyFilterStore' import { useSurveyFilterStore } from '@/store/surveyFilterStore'
@ -380,7 +387,7 @@ export function useSurvey(
}) })
/** /**
* @description * @description
* *
* @param {SurveyDetailRequest} surveyDetail * @param {SurveyDetailRequest} surveyDetail
* @returns {string} * @returns {string}
@ -395,15 +402,25 @@ export function useSurvey(
} }
const checkRequiredField = (field: string): string => { const checkRequiredField = (field: string): string => {
/** 기타 (직접입력) 항목이 있는 필드 체크 */
if (ETC_FIELDS.includes(field as (typeof ETC_FIELDS)[number])) { if (ETC_FIELDS.includes(field as (typeof ETC_FIELDS)[number])) {
/** 기타 (직접입력) 항목이 비어있거나 값 선택/입력을 하지 않은 경우 해당 필드 반환 */
if ( if (
isEmptyValue(surveyDetail[field as keyof SurveyDetailRequest]) && isEmptyValue(surveyDetail[field as keyof SurveyDetailRequest]) &&
isEmptyValue(surveyDetail[`${field}Etc` as keyof SurveyDetailRequest]) isEmptyValue(surveyDetail[`${field}Etc` as keyof SurveyDetailRequest])
) { ) {
return field return field
} }
/** - ,
* (O) /
* 2() /
*/
} else if (SPECIAL_CONDITIONS.includes(field as (typeof SPECIAL_CONDITIONS)[number])) { } 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])) { if (
(surveyDetail.constructionYear === selectBoxOptions.constructionYear[1].code && isEmptyValue(surveyDetail.constructionYearEtc)) ||
(surveyDetail.insulationPresence === '2' && isEmptyValue(surveyDetail.insulationPresenceEtc))
) {
/** 건축연수, 단열재 유무 필드가 비어있거나 값 선택/입력을 하지 않은 경우 해당 필드 반환 */
return `${field}Etc` return `${field}Etc`
} else if (isEmptyValue(surveyDetail[field as keyof SurveyDetailRequest])) { } else if (isEmptyValue(surveyDetail[field as keyof SurveyDetailRequest])) {
return field return field

View File

@ -2,4 +2,6 @@ export type CommCode = {
headCd: string headCd: string
code: string code: string
codeJp: string codeJp: string
refChr1: string
refNum1: string
} }

View File

@ -180,13 +180,17 @@ export type InquirySaveResponse = {
mailYn: string mailYn: string
} }
/** export const InquiryCommCode = {
* @description QNA_CLS_LRG_CD: {
*/ headCd: '204200',
export type CommonCode = { headId: 'QNA_CLS_LRG_CD',
headCd: string },
headId: string QNA_CLS_MID_CD: {
code: string headCd: '204300',
name: string headId: 'QNA_CLS_MID_CD',
refChar1: string },
QNA_CLS_SML_CD: {
headCd: '204400',
headId: 'QNA_CLS_SML_CD',
},
} }