refactor: improve error handling in API routes and services

- API 에러 시 반환 타입 통일
- Prisma 에러 검증 추가
This commit is contained in:
Dayoung 2025-06-18 15:12:45 +09:00
parent b116e6e5c1
commit d690c3e774
11 changed files with 221 additions and 112 deletions

View File

@ -3,6 +3,7 @@ import { SubmissionService } from './service'
import { HttpStatusCode } from 'axios'
import { ERROR_MESSAGES } from '@/utils/common-utils'
import { loggerWrapper } from '@/libs/api-wrapper'
import { ApiError } from 'next/dist/server/api-utils'
/**
* @api {GET} /api/submission
@ -50,8 +51,11 @@ async function getSubmitTargetData(request: NextRequest): Promise<NextResponse>
}
const submissionService = new SubmissionService(storeId, role)
const data = await submissionService.tryFunction(() => submissionService.getSubmissionTarget())
return NextResponse.json(data)
const result = await submissionService.tryFunction(() => submissionService.getSubmissionTarget())
if (result instanceof ApiError) {
return NextResponse.json({ error: result.message }, { status: result.statusCode })
}
return NextResponse.json(result)
}
export const GET = loggerWrapper(getSubmitTargetData)

View File

@ -2,7 +2,8 @@ import { prisma } from '@/libs/prisma'
import { ERROR_MESSAGES } from '@/utils/common-utils'
import { SubmitTargetResponse } from '@/types/Survey'
import { HttpStatusCode } from 'axios'
import { NextResponse } from 'next/server'
import { ApiError } from 'next/dist/server/api-utils'
import { Prisma } from '@prisma/client'
export class SubmissionService {
private storeId: string
@ -18,18 +19,26 @@ export class SubmissionService {
this.role = role
}
async getSubmissionTarget(): Promise<SubmitTargetResponse[] | null> {
/**
* @description
* @returns {Promise<SubmitTargetResponse[] | ApiError>}
*/
async getSubmissionTarget(): Promise<SubmitTargetResponse[] | ApiError> {
switch (this.role) {
case 'Admin_Sub':
return this.getSubmissionTargetAdminSub()
case 'Builder':
return this.getSubmissionTargetBuilder()
default:
return null
return new ApiError(HttpStatusCode.BadRequest, ERROR_MESSAGES.BAD_REQUEST)
}
}
private async getSubmissionTargetAdminSub(): Promise<SubmitTargetResponse[] | null> {
/**
* @description 2 (Admin_Sub - Musubi)
* @returns {Promise<SubmitTargetResponse[]>}
*/
private async getSubmissionTargetAdminSub(): Promise<SubmitTargetResponse[]> {
const query = `
SELECT
MCS.STORE_ID AS targetStoreId
@ -51,7 +60,11 @@ export class SubmissionService {
return await prisma.$queryRawUnsafe(this.BASE_QUERY + query, this.storeId)
}
private async getSubmissionTargetBuilder(): Promise<SubmitTargetResponse[] | null> {
/**
* @description 2 user의 (Builder - Musubi)
* @returns {Promise<SubmitTargetResponse[]>}
*/
private async getSubmissionTargetBuilder(): Promise<SubmitTargetResponse[]> {
const query = `
SELECT
MCAS.AGENCY_STORE_ID AS targetStoreId
@ -74,12 +87,31 @@ export class SubmissionService {
return await prisma.$queryRawUnsafe(this.BASE_QUERY + query, this.storeId)
}
handleRouteError(error: unknown): NextResponse {
/**
* @description API ROUTE
* @param error
* @returns {ApiError}
*/
handleRouteError(error: any): ApiError {
console.error('❌ API ROUTE ERROR : ', error)
return NextResponse.json({ error: ERROR_MESSAGES.FETCH_ERROR }, { status: HttpStatusCode.InternalServerError })
if (
error instanceof Prisma.PrismaClientInitializationError ||
error instanceof Prisma.PrismaClientUnknownRequestError ||
error instanceof Prisma.PrismaClientRustPanicError ||
error instanceof Prisma.PrismaClientValidationError ||
error instanceof Prisma.PrismaClientUnknownRequestError
) {
return new ApiError(HttpStatusCode.InternalServerError, ERROR_MESSAGES.PRISMA_ERROR)
}
return new ApiError(error.statusCode ?? HttpStatusCode.InternalServerError, error.message ?? ERROR_MESSAGES.FETCH_ERROR)
}
async tryFunction(func: () => Promise<any>): Promise<any> {
/**
* @description try-catch
* @param func
* @returns {Promise<ApiError | any>}
*/
async tryFunction(func: () => Promise<any>): Promise<ApiError | any> {
try {
return await func()
} catch (error) {

View File

@ -4,9 +4,9 @@ import { sessionOptions } from '@/libs/session'
import { cookies } from 'next/headers'
import type { SessionData } from '@/types/Auth'
import { HttpStatusCode } from 'axios'
import { ERROR_MESSAGES } from '@/utils/common-utils'
import { loggerWrapper } from '@/libs/api-wrapper'
import { SurveySalesService } from '../service'
import { ApiError } from 'next/dist/server/api-utils'
/**
* @api {GET} /api/survey-sales/:id API
@ -51,22 +51,12 @@ async function getSurveySaleDetail(request: NextRequest): Promise<NextResponse>
const { searchParams } = new URL(request.url)
const isPdf = searchParams.get('isPdf') === 'true'
const service = new SurveySalesService({})
const survey = await service.tryFunction(() => service.fetchSurvey(Number(id)))
if (!survey) {
return NextResponse.json({ error: ERROR_MESSAGES.NOT_FOUND }, { status: HttpStatusCode.NotFound })
const service = new SurveySalesService({}, session)
const result = await service.tryFunction(() => service.fetchSurvey(Number(id), isPdf))
if (result instanceof ApiError) {
return NextResponse.json({ error: result.message }, { status: result.statusCode })
}
if (isPdf || service.checkRole(survey, session)) {
return NextResponse.json(survey)
}
if (!session?.isLoggedIn || session?.role === null) {
return NextResponse.json({ error: ERROR_MESSAGES.UNAUTHORIZED }, { status: HttpStatusCode.Unauthorized })
}
return NextResponse.json({ error: ERROR_MESSAGES.NO_PERMISSION }, { status: HttpStatusCode.Forbidden })
return NextResponse.json(result)
}
/**
@ -107,8 +97,11 @@ async function updateSurveySaleDetail(request: NextRequest): Promise<NextRespons
const body = await request.json()
const service = new SurveySalesService({})
const survey = await service.tryFunction(() => service.updateSurvey(Number(id), body.survey, body.isTemporary, body.storeId, body.role))
return NextResponse.json(survey)
const result = await service.tryFunction(() => service.updateSurvey(Number(id), body.survey, body.isTemporary, body.storeId, body.role))
if (result instanceof ApiError) {
return NextResponse.json({ error: result.message }, { status: result.statusCode })
}
return NextResponse.json(result)
}
/**
@ -132,7 +125,10 @@ async function updateSurveySaleDetail(request: NextRequest): Promise<NextRespons
async function deleteSurveySaleDetail(request: NextRequest): Promise<NextResponse> {
const id = request.nextUrl.pathname.split('/').pop() ?? ''
const service = new SurveySalesService({})
await service.tryFunction(() => service.deleteSurvey(Number(id)))
const result = await service.tryFunction(() => service.deleteSurvey(Number(id)))
if (result instanceof ApiError) {
return NextResponse.json({ error: result.message }, { status: result.statusCode })
}
return NextResponse.json({ status: HttpStatusCode.Ok })
}
@ -176,8 +172,11 @@ async function submitSurveySaleDetail(request: NextRequest): Promise<NextRespons
const body = await request.json()
const service = new SurveySalesService({})
const survey = await service.tryFunction(() => service.submitSurvey(Number(id), body.targetId, body.targetNm))
return NextResponse.json({ status: HttpStatusCode.Ok, data: survey })
const result = await service.tryFunction(() => service.submitSurvey(Number(id), body.targetId, body.targetNm))
if (result instanceof ApiError) {
return NextResponse.json({ error: result.message }, { status: result.statusCode })
}
return NextResponse.json({ status: HttpStatusCode.Ok, data: result })
}
export const GET = loggerWrapper(getSurveySaleDetail)

View File

@ -1,9 +1,12 @@
import { NextResponse } from 'next/server'
import { ERROR_MESSAGES } from '@/utils/common-utils'
import { loggerWrapper } from '@/libs/api-wrapper'
import { HttpStatusCode } from 'axios'
import { SurveySearchParams } from '@/types/Survey'
import { SurveySalesService } from './service'
import { ApiError } from 'next/dist/server/api-utils'
import { getIronSession } from 'iron-session'
import { sessionOptions } from '@/libs/session'
import { cookies } from 'next/headers'
import { SessionData } from '@/types/Auth'
/**
* @api {GET} /api/survey-sales API
@ -48,7 +51,9 @@ import { SurveySalesService } from './service'
*
*/
async function getSurveySales(request: Request) {
/** URL 파라미터 파싱 */
const cookieStore = await cookies()
const session = await getIronSession<SessionData>(cookieStore, sessionOptions)
const { searchParams } = new URL(request.url)
const params: SurveySearchParams = {
keyword: searchParams.get('keyword'),
@ -56,19 +61,13 @@ async function getSurveySales(request: Request) {
isMySurvey: searchParams.get('isMySurvey'),
sort: searchParams.get('sort'),
offset: searchParams.get('offset'),
role: searchParams.get('role'),
storeId: searchParams.get('storeId'),
builderId: searchParams.get('builderId'),
}
const surveySalesService = new SurveySalesService(params)
const surveySalesService = new SurveySalesService(params, session)
/** 세션 체크 결과 처리 */
const sessionCheckResult = surveySalesService.checkSession()
if (sessionCheckResult) {
return sessionCheckResult
const result = await surveySalesService.tryFunction(() => surveySalesService.getSurveySales())
if (result instanceof ApiError) {
return NextResponse.json({ error: result.message }, { status: result.statusCode })
}
const where = surveySalesService.createFilterSurvey()
const result = await surveySalesService.tryFunction(() => surveySalesService.getSurveySales(where))
return NextResponse.json({ data: result })
}
@ -112,6 +111,9 @@ async function createSurveySales(request: Request) {
const body = await request.json()
const surveySalesService = new SurveySalesService({})
const result = await surveySalesService.tryFunction(() => surveySalesService.createSurvey(body.survey, body.role, body.storeId))
if (result instanceof ApiError) {
return NextResponse.json({ error: result.message }, { status: result.statusCode })
}
return NextResponse.json(result)
}

View File

@ -1,10 +1,10 @@
import { prisma } from '@/libs/prisma'
import { SurveyBasicInfo, SurveyRegistRequest, SurveySearchParams } from '@/types/Survey'
import { convertToSnakeCase, ERROR_MESSAGES } from '@/utils/common-utils'
import { NextResponse } from 'next/server'
import { Prisma } from '@prisma/client'
import type { SessionData } from '@/types/Auth'
import { HttpStatusCode } from 'axios'
import { ApiError } from 'next/dist/server/api-utils'
type WhereCondition = {
AND: any[]
@ -36,13 +36,15 @@ const ITEMS_PER_PAGE = 10
*/
export class SurveySalesService {
private params!: SurveySearchParams
private session?: SessionData
/**
* @description
* @param {SurveySearchParams} params
*/
constructor(params: SurveySearchParams) {
constructor(params: SurveySearchParams, session?: SessionData) {
this.params = params
this.session = session
}
/**
@ -50,17 +52,17 @@ export class SurveySalesService {
* @param {SearchParams} params
* @returns {NextResponse}
*/
checkSession() {
if (this.params.role === null) {
return NextResponse.json({ data: [], count: 0 })
checkSession(): ApiError | null {
if (!this.session?.isLoggedIn || this.session?.role === null) {
return new ApiError(HttpStatusCode.Unauthorized, ERROR_MESSAGES.UNAUTHORIZED)
}
if (this.params.role === 'Builder' || this.params.role === 'Partner') {
if (this.session?.role === 'Builder' || this.session?.role === 'Partner') {
if (this.params.builderId === null) {
return NextResponse.json({ data: [], count: 0 })
return new ApiError(HttpStatusCode.Forbidden, ERROR_MESSAGES.NO_PERMISSION)
}
} else {
if (this.params.storeId === null) {
return NextResponse.json({ data: [], count: 0 })
if (this.session?.storeId === null) {
return new ApiError(HttpStatusCode.Forbidden, ERROR_MESSAGES.NO_PERMISSION)
}
}
return null
@ -107,19 +109,21 @@ export class SurveySalesService {
private createRoleCondition(): WhereCondition {
const where: WhereCondition = { AND: [] }
switch (this.params.role) {
switch (this.session?.role) {
case 'Admin':
where.OR = [
{ AND: [{ STORE_ID: { equals: this.params.storeId } }] },
{ AND: [{ SUBMISSION_TARGET_ID: { equals: this.params.storeId } }, { SUBMISSION_STATUS: { equals: true } }] },
{ AND: [{ STORE_ID: { equals: this.session?.storeId } }] },
{ AND: [{ SUBMISSION_TARGET_ID: { equals: this.session?.storeId } }, { SUBMISSION_STATUS: { equals: true } }] },
]
break
case 'Admin_Sub':
where.OR = [
{ AND: [{ STORE_ID: { equals: this.params.storeId } }, { CONSTRUCTION_POINT_ID: { equals: this.params.builderId } }] },
{
AND: [{ STORE_ID: { equals: this.session?.storeId } }, { CONSTRUCTION_POINT_ID: { equals: this.session?.builderId } }],
},
{
AND: [
{ SUBMISSION_TARGET_ID: { equals: this.params.storeId } },
{ SUBMISSION_TARGET_ID: { equals: this.session?.storeId } },
{ CONSTRUCTION_POINT_ID: { not: null } },
{ CONSTRUCTION_POINT_ID: { not: '' } },
{ SUBMISSION_STATUS: { equals: true } },
@ -129,10 +133,10 @@ export class SurveySalesService {
break
case 'Builder':
case 'Partner':
where.AND.push({ CONSTRUCTION_POINT_ID: { equals: this.params.builderId } })
where.AND.push({ CONSTRUCTION_POINT_ID: { equals: this.session?.builderId } })
break
case 'T01':
where.OR = [{ NOT: { SRL_NO: { startsWith: '一時保存' } } }, { STORE_ID: { equals: this.params.storeId } }]
where.OR = [{ NOT: { SRL_NO: { startsWith: '一時保存' } } }, { STORE_ID: { equals: this.session?.storeId } }]
break
}
return where
@ -162,7 +166,6 @@ export class SurveySalesService {
if (Object.keys(roleCondition).length > 0) {
where.AND.push(roleCondition)
}
return where
}
@ -171,7 +174,13 @@ export class SurveySalesService {
* @param {WhereCondition} where
* @returns {Promise<{ data: SurveyBasicInfo[], count: number }>}
*/
async getSurveySales(where: WhereCondition) {
async getSurveySales(): Promise<{ data: SurveyBasicInfo[]; count: number } | ApiError> {
const sessionCheckResult = this.checkSession()
if (sessionCheckResult) {
return sessionCheckResult
}
const where = this.createFilterSurvey()
/** 조사 매물 조회 */
//@ts-ignore
const surveys = await prisma.SD_SURVEY_SALES_BASIC_INFO.findMany({
@ -245,12 +254,20 @@ export class SurveySalesService {
* @param {number} id ID
* @returns {Promise<SurveyBasicInfo>}
*/
async fetchSurvey(id: number) {
async fetchSurvey(id: number, isPdf: boolean): Promise<SurveyBasicInfo | ApiError> {
// @ts-ignore
return (await prisma.SD_SURVEY_SALES_BASIC_INFO.findFirst({
const result = await prisma.SD_SURVEY_SALES_BASIC_INFO.findFirst({
where: { ID: id },
include: { DETAIL_INFO: true },
})) as SurveyBasicInfo
})
if (!result) {
return new ApiError(HttpStatusCode.NotFound, ERROR_MESSAGES.NOT_FOUND)
}
if (!isPdf) {
if (!this.session?.isLoggedIn) return new ApiError(HttpStatusCode.Unauthorized, ERROR_MESSAGES.UNAUTHORIZED)
if (!this.checkRole(result, this.session as SessionData)) return new ApiError(HttpStatusCode.Forbidden, ERROR_MESSAGES.NO_PERMISSION)
}
return result
}
/**
@ -339,7 +356,7 @@ export class SurveySalesService {
* @returns {boolean} (true: , false: )
*/
checkRole(survey: any, session: SessionData): boolean {
if (!survey || !session.isLoggedIn) return false
if (!survey || !session) return false
const roleChecks = {
T01: () => this.checkT01Role(survey),
@ -352,15 +369,38 @@ export class SurveySalesService {
return roleChecks[session.role as keyof typeof roleChecks]?.() ?? false
}
/**
* @description T01
* -
*
* @param {any} survey
* @returns {boolean} (true: , false: )
*/
private checkT01Role(survey: any): boolean {
return survey.SRL_NO !== '一時保存'
}
/**
* @description Admin (1 - Order)
* - ,
*
* @param {any} survey
* @param {string | null} storeId ID
* @returns {boolean} (true: , false: )
*/
private checkAdminRole(survey: any, storeId: string | null): boolean {
if (!storeId) return false
return survey.SUBMISSION_STATUS ? survey.SUBMISSION_TARGET_ID === storeId || survey.STORE_ID === storeId : survey.STORE_ID === storeId
}
/**
* @description Admin_Sub (2 - Musubi)
* - , user에게
*
* @param {any} survey
* @param {string | null} storeId ID
* @returns {boolean} (true: , false: )
*/
private checkAdminSubRole(survey: any, storeId: string | null): boolean {
if (!storeId) return false
return survey.SUBMISSION_STATUS
@ -368,17 +408,44 @@ export class SurveySalesService {
: survey.STORE_ID === storeId && !survey.CONSTRUCTION_POINT_ID
}
/**
* @description Partner Builder
* -
*
* @param {any} survey
* @param {string | null} builderId ID
* @returns {boolean} (true: , false: )
*/
private checkPartnerOrBuilderRole(survey: any, builderId: string | null): boolean {
if (!builderId) return false
return survey.CONSTRUCTION_POINT_ID === builderId
}
handleRouteError(error: unknown): NextResponse {
/**
* @description API ROUTE
* @param {any} error
* @returns {ApiError}
*/
handleRouteError(error: any): ApiError {
console.error('❌ API ROUTE ERROR : ', error)
return NextResponse.json({ error: ERROR_MESSAGES.FETCH_ERROR }, { status: HttpStatusCode.InternalServerError })
if (
error instanceof Prisma.PrismaClientInitializationError ||
error instanceof Prisma.PrismaClientUnknownRequestError ||
error instanceof Prisma.PrismaClientRustPanicError ||
error instanceof Prisma.PrismaClientValidationError ||
error instanceof Prisma.PrismaClientUnknownRequestError
) {
return new ApiError(HttpStatusCode.InternalServerError, ERROR_MESSAGES.PRISMA_ERROR)
}
return new ApiError(error.statusCode ?? HttpStatusCode.InternalServerError, error.message ?? ERROR_MESSAGES.FETCH_ERROR)
}
async tryFunction(func: () => Promise<any>): Promise<any> {
/**
* @description try-catch
* @param {() => Promise<any>} func
* @returns {Promise<ApiError | any>}
*/
async tryFunction(func: () => Promise<any>): Promise<ApiError | any> {
try {
return await func()
} catch (error) {

View File

@ -7,13 +7,14 @@ import { useSurvey } from '@/hooks/useSurvey'
import { radioEtcData, roofMaterial, selectBoxOptions, supplementaryFacilities } from '../survey-sale/detail/RoofForm'
import { useSpinnerStore } from '@/store/spinnerStore'
import { useSessionStore } from '@/store/session'
import { SURVEY_ALERT_MSG } from '@/types/Survey'
export default function SurveySaleDownloadPdf() {
const params = useParams()
const id = params.id
const router = useRouter()
const { surveyDetail, isLoadingSurveyDetail } = useSurvey(Number(id), true)
const { surveyDetail, isLoadingSurveyDetail, showSurveyAlert } = useSurvey(Number(id), true)
const { setIsShow } = useSpinnerStore()
const { session } = useSessionStore()
@ -23,11 +24,6 @@ export default function SurveySaleDownloadPdf() {
/** 페이지 랜더링 이후 PDF 생성 */
useEffect(() => {
if (isLoadingSurveyDetail || isGeneratedRef.current) return
if (surveyDetail === null) {
alert('データが見つかりません。')
router.replace('/')
return
}
isGeneratedRef.current = true
handleDownPdf()
}, [surveyDetail?.id, isLoadingSurveyDetail])
@ -65,10 +61,11 @@ export default function SurveySaleDownloadPdf() {
} else {
router.replace('/')
}
alert('PDFの生成が完了しました。 ポップアップウィンドウからダウンロードしてください。')
showSurveyAlert('PDFの生成が完了しました。 ポップアップウィンドウからダウンロードしてください。')
})
.catch((error: any) => {
console.error('error', error)
console.error('❌ PDF GENERATION ERROR', error)
showSurveyAlert(SURVEY_ALERT_MSG.PDF_GENERATION_ERROR)
})
}

View File

@ -6,7 +6,7 @@ import { useEffect, useState } from 'react'
import { useParams, useRouter } from 'next/navigation'
import { requiredFields, useSurvey } from '@/hooks/useSurvey'
import { usePopupController } from '@/store/popupController'
import { ALERT_MESSAGES } from '@/types/Survey'
import { SURVEY_ALERT_MSG } from '@/types/Survey'
interface ButtonFormProps {
mode: Mode
@ -117,7 +117,7 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) {
router.push(`/survey-sale/${savedId}`)
}
}
showSurveyAlert(ALERT_MESSAGES.TEMP_SAVE_SUCCESS)
showSurveyAlert(SURVEY_ALERT_MSG.TEMP_SAVE_SUCCESS)
}
/** 입력 필드 포커스 처리 */
@ -130,7 +130,7 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) {
const saveProcess = async (emptyField: string | null, isSubmitProcess?: boolean) => {
if (emptyField?.trim() === '') {
if (!isSubmitProcess) {
showSurveyConfirm(ALERT_MESSAGES.SAVE_CONFIRM, async () => {
showSurveyConfirm(SURVEY_ALERT_MSG.SAVE_CONFIRM, async () => {
await handleSuccessfulSave(isSubmitProcess)
})
} else {
@ -155,7 +155,7 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) {
if (isSubmitProcess) {
popupController.setSurveySaleSubmitPopup(true)
} else {
showSurveyAlert(ALERT_MESSAGES.SAVE_SUCCESS)
showSurveyAlert(SURVEY_ALERT_MSG.SAVE_SUCCESS)
}
}
} else {
@ -165,7 +165,7 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) {
await router.push(`/survey-sale/${savedId}?show=true`)
} else {
await router.push(`/survey-sale/${savedId}`)
showSurveyAlert(ALERT_MESSAGES.SAVE_SUCCESS)
showSurveyAlert(SURVEY_ALERT_MSG.SAVE_SUCCESS)
}
}
}
@ -173,10 +173,10 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) {
/** 필수값 미입력 처리 */
const handleFailedSave = (emptyField: string | null) => {
if (emptyField?.includes('Unit')) {
showSurveyAlert(ALERT_MESSAGES.UNIT_REQUIRED)
showSurveyAlert(SURVEY_ALERT_MSG.UNIT_REQUIRED)
} else {
const fieldInfo = requiredFields.find((field) => field.field === emptyField)
showSurveyAlert(ALERT_MESSAGES.REQUIRED_FIELD, fieldInfo?.name || '')
showSurveyAlert(SURVEY_ALERT_MSG.REQUIRED_FIELD, fieldInfo?.name || '')
}
focusInput(emptyField as keyof SurveyDetailInfo)
}
@ -184,10 +184,10 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) {
/** 삭제 로직 */
const handleDelete = async () => {
if (!Number.isNaN(id)) {
showSurveyConfirm(ALERT_MESSAGES.DELETE_CONFIRM, async () => {
showSurveyConfirm(SURVEY_ALERT_MSG.DELETE_CONFIRM, async () => {
await deleteSurvey()
if (!isDeletingSurvey) {
showSurveyAlert(ALERT_MESSAGES.DELETE_SUCCESS)
showSurveyAlert(SURVEY_ALERT_MSG.DELETE_SUCCESS)
router.push('/survey-sale')
}
})
@ -197,16 +197,16 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) {
/** 제출 로직 */
const handleSubmit = async () => {
if (data.basic.srlNo?.startsWith('一時保存') && Number.isNaN(id)) {
showSurveyAlert(ALERT_MESSAGES.TEMP_SAVE_SUBMIT_ERROR)
showSurveyAlert(SURVEY_ALERT_MSG.TEMP_SAVE_SUBMIT_ERROR)
return
}
if (mode === 'READ') {
showSurveyConfirm(ALERT_MESSAGES.SUBMIT_CONFIRM, async () => {
showSurveyConfirm(SURVEY_ALERT_MSG.SUBMIT_CONFIRM, async () => {
popupController.setSurveySaleSubmitPopup(true)
})
} else {
showSurveyConfirm(ALERT_MESSAGES.SAVE_AND_SUBMIT_CONFIRM, async () => {
showSurveyConfirm(SURVEY_ALERT_MSG.SAVE_AND_SUBMIT_CONFIRM, async () => {
handleSave(false, true)
})
}

View File

@ -1,7 +1,7 @@
import { useState } from 'react'
import type { Mode, SurveyDetailInfo, SurveyDetailRequest } from '@/types/Survey'
import { useSurvey } from '@/hooks/useSurvey'
import { ALERT_MESSAGES } from '@/types/Survey'
import { SURVEY_ALERT_MSG } from '@/types/Survey'
type RadioEtcKeys =
| 'structureOrder'
@ -755,7 +755,7 @@ const MultiCheck = ({
if (isRoofMaterial) {
const totalSelected = selectedValues.length + (isOtherSelected || isOtherCheck ? 1 : 0)
if (totalSelected >= 2) {
showSurveyAlert(ALERT_MESSAGES.ROOF_MATERIAL_MAX_SELECT_ERROR)
showSurveyAlert(SURVEY_ALERT_MSG.ROOF_MATERIAL_MAX_SELECT_ERROR)
return
}
}
@ -769,7 +769,7 @@ const MultiCheck = ({
if (isRoofMaterial) {
const currentSelected = selectedValues.length
if (!isOtherCheck && currentSelected >= 2) {
showSurveyAlert(ALERT_MESSAGES.ROOF_MATERIAL_MAX_SELECT_ERROR)
showSurveyAlert(SURVEY_ALERT_MSG.ROOF_MATERIAL_MAX_SELECT_ERROR)
return
}
}

View File

@ -1,4 +1,4 @@
import { ALERT_MESSAGES, type SubmitTargetResponse, type SurveyBasicInfo, type SurveyDetailRequest, type SurveyRegistRequest } from '@/types/Survey'
import { SURVEY_ALERT_MSG, type SubmitTargetResponse, type SurveyBasicInfo, type SurveyDetailRequest, type SurveyRegistRequest } from '@/types/Survey'
import { useMemo } from 'react'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { useSurveyFilterStore } from '@/store/surveyFilterStore'
@ -7,6 +7,8 @@ import { useAxios } from './useAxios'
import { queryStringFormatter } from '@/utils/common-utils'
import { useRouter } from 'next/navigation'
export const requiredFields = [
{
field: 'installationSystem',
@ -93,7 +95,7 @@ export function useSurvey(
refetchSurveyList: () => void
refetchSurveyDetail: () => void
getSubmitTarget: (params: { storeId: string; role: string }) => Promise<SubmitTargetResponse[] | null>
showSurveyAlert: (message: (typeof ALERT_MESSAGES)[keyof typeof ALERT_MESSAGES] | string, requiredField?: string) => void
showSurveyAlert: (message: (typeof SURVEY_ALERT_MSG)[keyof typeof SURVEY_ALERT_MSG] | string, requiredField?: string) => void
showSurveyConfirm: (message: string, onConfirm: () => void, onCancel?: () => void) => void
} {
const queryClient = useQueryClient()
@ -106,7 +108,8 @@ export function useSurvey(
* @description ,
*
* @param {any} error
* @returns {void}
* @param {boolean} isThrow Throw
* @returns {void} / Throw
*/
const handleError = (error: any, isThrow?: boolean) => {
const status = error.response?.status
@ -175,7 +178,7 @@ export function useSurvey(
isLoading: isLoadingSurveyList,
refetch: refetchSurveyList,
} = useQuery({
queryKey: ['survey', 'list', keyword, searchOption, isMySurvey, sort, offset, session?.storeNm, session?.builderId, session?.role],
queryKey: ['survey', 'list', keyword, searchOption, isMySurvey, sort, offset],
queryFn: async () => {
return await tryFunction(
() =>
@ -186,15 +189,13 @@ export function useSurvey(
isMySurvey,
sort,
offset,
storeId: session?.storeId,
builderId: session?.builderId,
role: session?.role,
},
}),
true,
false,
)
},
enabled: !isPdf,
})
/**
@ -436,7 +437,10 @@ export function useSurvey(
* @param {string} message
* @param {string} [requiredField]
*/
const showSurveyAlert = (message: (typeof ALERT_MESSAGES)[keyof typeof ALERT_MESSAGES] | string, requiredField?: (typeof requiredFields)[number]['field']) => {
const showSurveyAlert = (
message: (typeof SURVEY_ALERT_MSG)[keyof typeof SURVEY_ALERT_MSG] | string,
requiredField?: (typeof requiredFields)[number]['field'],
) => {
if (requiredField) {
alert(`${requiredField} ${message}`)
} else {

View File

@ -325,7 +325,7 @@ export type SurveySearchParams = {
}
export const ALERT_MESSAGES = {
export const SURVEY_ALERT_MSG = {
/** 기본 메세지 */
/** 저장 성공 - "저장되었습니다." */
SAVE_SUCCESS: '保存されました。',
@ -351,4 +351,7 @@ export const ALERT_MESSAGES = {
REQUIRED_FIELD: '項目が空です。',
/** 최대 선택 오류 메세지 - "지붕재는 최대 2개까지 선택할 수 있습니다." */
ROOF_MATERIAL_MAX_SELECT_ERROR: '屋根材は最大2個まで選択できます。',
/** PDF 생성 오류 - "PDF 생성에 실패했습니다." */
PDF_GENERATION_ERROR: 'PDF 生成に失敗しました。',
}

View File

@ -155,7 +155,7 @@ export const unescapeString = (str) => {
*/
while (regex.test(str)) {
str = str.replace(regex, (matched) => chars[matched] || matched);
str = str.replace(regex, (matched) => chars[matched] || matched)
}
return str
}
@ -186,29 +186,28 @@ function isObject(value) {
return value !== null && typeof value === 'object'
}
// 카멜케이스를 스네이크케이스로 변환하는 함수
export const toSnakeCase = (str) => {
return str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)
}
// 객체의 키를 스네이크케이스로 변환하는 함수
export const convertToSnakeCase = (obj) => {
if (obj === null || obj === undefined) return obj;
if (obj === null || obj === undefined) return obj
if (Array.isArray(obj)) {
return obj.map((item) => convertToSnakeCase(item))
}
if (typeof obj === 'object') {
return Object.keys(obj).reduce((acc, key) => {
const snakeKey = toSnakeCase(key).toUpperCase();
acc[snakeKey] = convertToSnakeCase(obj[key]);
return acc;
}, {});
const snakeKey = toSnakeCase(key).toUpperCase()
acc[snakeKey] = convertToSnakeCase(obj[key])
return acc
}, {})
}
return obj;
return obj
}
/**
@ -225,4 +224,6 @@ export const ERROR_MESSAGES = {
FETCH_ERROR: 'データの取得に失敗しました。',
/** 잘못된 요청입니다. */
BAD_REQUEST: '間違ったリクエストです。',
/** 데이터베이스 오류가 발생했습니다. */
PRISMA_ERROR: 'データベース エラーが発生しました。',
}