diff --git a/src/app/api/qna/save/route.ts b/src/app/api/qna/save/route.ts index 05d0595..85fbc6f 100644 --- a/src/app/api/qna/save/route.ts +++ b/src/app/api/qna/save/route.ts @@ -7,6 +7,7 @@ import { getIronSession } from 'iron-session' import { SessionData } from '@/types/Auth' import { sessionOptions } from '@/libs/session' import { cookies } from 'next/headers' +import { ERROR_MESSAGE } from '@/hooks/useAlertMsg' /** * @api {POST} /api/qna/save 문의 저장 API @@ -37,7 +38,7 @@ async function setQna(request: Request): Promise { const formData = await request.formData() const mailResult = await service.sendMail(formData) if (mailResult instanceof ApiError) { - return NextResponse.json({ error: mailResult.message }, { status: mailResult.statusCode }) + return NextResponse.json({ error: ERROR_MESSAGE.EMAIL_SEND_ERROR }, { status: HttpStatusCode.InternalServerError }) } const result = await service.tryFunction(() => diff --git a/src/app/api/qna/service.ts b/src/app/api/qna/service.ts index be3d538..5558a1e 100644 --- a/src/app/api/qna/service.ts +++ b/src/app/api/qna/service.ts @@ -35,6 +35,9 @@ export class QnaService { if (shouldThrowResult) return response return this.handleResult(response) } catch (error) { + if (shouldThrowResult) { + return new ApiError(HttpStatusCode.InternalServerError, ERROR_MESSAGE.SERVER_ERROR) + } return this.handleRouteError(error) } } diff --git a/src/components/inquiry/RegistForm.tsx b/src/components/inquiry/RegistForm.tsx index 1624822..f448aa3 100644 --- a/src/components/inquiry/RegistForm.tsx +++ b/src/components/inquiry/RegistForm.tsx @@ -5,7 +5,7 @@ import { useSessionStore } from '@/store/session' import { InquiryRequest } from '@/types/Inquiry' import { useEffect, useState } from 'react' import { useRouter } from 'next/navigation' -import { CONFIRM_MESSAGE, ERROR_MESSAGE, SUCCESS_MESSAGE, useAlertMsg, WARNING_MESSAGE } from '@/hooks/useAlertMsg' +import { CONFIRM_MESSAGE, SUCCESS_MESSAGE, useAlertMsg, WARNING_MESSAGE } from '@/hooks/useAlertMsg' import { useInquiryFilterStore } from '@/store/inquiryFilterStore' export default function RegistForm() { @@ -106,12 +106,10 @@ export default function RegistForm() { showConfirm( CONFIRM_MESSAGE.SAVE_INQUIRY_CONFIRM, async () => { - try { - const res = await saveInquiry(formData) + const res = await saveInquiry(formData) + if (!isSavingInquiry) { showSuccessAlert(SUCCESS_MESSAGE.SAVE_SUCCESS) router.push(`/inquiry/${res.qnaNo}`) - } catch (error) { - showErrorAlert(ERROR_MESSAGE.SERVER_ERROR) } }, () => null, diff --git a/src/components/popup/SurveySaleSubmitPopup.tsx b/src/components/popup/SurveySaleSubmitPopup.tsx index 2093c38..db5cbe3 100644 --- a/src/components/popup/SurveySaleSubmitPopup.tsx +++ b/src/components/popup/SurveySaleSubmitPopup.tsx @@ -19,6 +19,7 @@ interface SubmitFormData { reference: string[] | null title: string contents: string | null + srlNo: string | null } interface FormField { @@ -35,7 +36,7 @@ export default function SurveySaleSubmitPopup() { const { setIsShow } = useSpinnerStore() const { getCommCode } = useCommCode() - const { surveyDetail, getSubmitTarget } = useSurvey(Number(routeId)) + const { surveyDetail, getSubmitTarget, isSubmittingSurvey, submitSurvey } = useSurvey(Number(routeId)) const { showErrorAlert, showSuccessAlert, showConfirm } = useAlertMsg() const [submitData, setSubmitData] = useState({ @@ -47,6 +48,7 @@ export default function SurveySaleSubmitPopup() { reference: null, title: '', contents: '', + srlNo: null, }) const [commCodeList, setCommCodeList] = useState([]) @@ -56,6 +58,7 @@ export default function SurveySaleSubmitPopup() { const baseUpdate = { sender: session?.email ?? '', title: '[HANASYS現地調査] 調査物件が提出. (' + surveyDetail?.srlNo + ')', + srlNo: surveyDetail?.srlNo ?? null, } /** Admin 제출 폼 데이터 삽입 - 1차 판매점*/ if (session?.role === 'Admin') { @@ -106,8 +109,6 @@ export default function SurveySaleSubmitPopup() { { id: 'contents', name: '内容', required: false }, ] - const { submitSurvey, isSubmittingSurvey } = useSurvey(Number(routeId)) - const handleInputChange = (field: keyof SubmitFormData, value: string) => { setSubmitData((prev) => ({ ...prev, [field]: value })) } @@ -140,11 +141,15 @@ export default function SurveySaleSubmitPopup() { cc: submitData.reference ?? '', subject: submitData.title, content: generateEmailContent(), + surveyPdf: { + id: surveyDetail?.id ?? 0, + filename: surveyDetail?.srlNo ?? 'hanasys_survey', + }, }) .then(() => { + submitSurvey({ targetId: submitData.targetId, targetNm: submitData.targetNm }) if (!isSubmittingSurvey) { showSuccessAlert(SUCCESS_MESSAGE.SUBMIT_SUCCESS) - submitSurvey({ targetId: submitData.targetId, targetNm: submitData.targetNm }) popupController.setSurveySaleSubmitPopup(false) } }) diff --git a/src/components/survey-sale/detail/ButtonForm.tsx b/src/components/survey-sale/detail/ButtonForm.tsx index b52e071..d7063da 100644 --- a/src/components/survey-sale/detail/ButtonForm.tsx +++ b/src/components/survey-sale/detail/ButtonForm.tsx @@ -48,10 +48,12 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) { const isSubmit = data.basic.submissionStatus - const { deleteSurvey, updateSurvey, isDeletingSurvey, isUpdatingSurvey } = useSurvey(id) + const { deleteSurvey, updateSurvey, isDeletingSurvey, isUpdatingSurvey, isSubmittingSurvey, isLoadingSurveyDetail } = useSurvey(id) const { validateSurveyDetail, createSurvey, isCreatingSurvey } = useSurvey() const { showErrorAlert, showSuccessAlert, showConfirm } = useAlertMsg() + const buttonDisabled = isLoadingSurveyDetail || isSubmittingSurvey || isCreatingSurvey || isUpdatingSurvey || isDeletingSurvey + useEffect(() => { if (!session?.isLoggedIn) return @@ -237,9 +239,9 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) {
- {(permissions.isWriter || permissions.isSubmiter || (permissions.isReceiver && isSubmit)) && } - {(permissions.isWriter || (permissions.isReceiver && isSubmit)) && } - {!isSubmit && permissions.isSubmiter && } + {(permissions.isWriter || permissions.isSubmiter || (permissions.isReceiver && isSubmit)) && } + {(permissions.isWriter || (permissions.isReceiver && isSubmit)) && } + {!isSubmit && permissions.isSubmiter && }
)} @@ -252,9 +254,9 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) {
- handleSave(true, false)} /> - handleSave(false, false)} /> - {!isSubmit && permissions.isSubmiter && } + handleSave(true, false)} disabled={buttonDisabled} /> + handleSave(false, false)} disabled={buttonDisabled} /> + {!isSubmit && permissions.isSubmiter && }
)} @@ -274,7 +276,7 @@ const ListButton = () => { ) } -const EditButton = ({ setMode }: { setMode: (mode: Mode) => void }) => { +const EditButton = ({ setMode, disabled }: { setMode: (mode: Mode) => void; disabled: boolean }) => { return (
@@ -289,33 +292,33 @@ const EditButton = ({ setMode }: { setMode: (mode: Mode) => void }) => { ) } -const SubmitButton = ({ handleSubmit }: { handleSubmit: () => void }) => ( +const SubmitButton = ({ handleSubmit, disabled }: { handleSubmit: () => void; disabled: boolean }) => (
-
) -const DeleteButton = ({ handleDelete }: { handleDelete: () => void }) => ( +const DeleteButton = ({ handleDelete, disabled }: { handleDelete: () => void; disabled: boolean }) => (
-
) -const SaveButton = ({ handleSave }: { handleSave: () => void }) => ( +const SaveButton = ({ handleSave, disabled }: { handleSave: () => void; disabled: boolean }) => (
-
) -const TempButton = ({ handleSave }: { handleSave: () => void }) => ( +const TempButton = ({ handleSave, disabled }: { handleSave: () => void; disabled: boolean }) => (
-
diff --git a/src/hooks/useInquiry.ts b/src/hooks/useInquiry.ts index 50b309e..614376d 100644 --- a/src/hooks/useInquiry.ts +++ b/src/hooks/useInquiry.ts @@ -5,7 +5,7 @@ import { useInquiryFilterStore } from '@/store/inquiryFilterStore' import { useMemo } from 'react' import { useRouter } from 'next/navigation' import { useAlertMsg } from '@/hooks/useAlertMsg' -import { CommCode } from '@/types/CommCode' + /** * @description 문의사항 관련 기능을 제공하는 커스텀 훅 diff --git a/src/libs/mailer.ts b/src/libs/mailer.ts index 727e7a3..10baec3 100644 --- a/src/libs/mailer.ts +++ b/src/libs/mailer.ts @@ -3,6 +3,15 @@ import nodemailer from 'nodemailer' import { Attachment } from 'nodemailer/lib/mailer' +/** + * @description 이메일 파라미터 인터페이스 + * @param {string} from 발신자 + * @param {string | string[]} to 수신자 + * @param {string | string[]} cc 참조 + * @param {string} subject 제목 + * @param {string} content 내용 + * @param {Attachment[]} attachments 첨부파일 + */ interface EmailParams { from: string to: string | string[] @@ -10,10 +19,32 @@ interface EmailParams { subject: string content: string attachments?: Attachment[] + surveyPdf?: { + id: number + filename: string + } } -export async function sendEmail({ from, to, cc, subject, content, attachments }: EmailParams): Promise { - // Create a transporter using SMTP +export async function sendEmail({ from, to, cc, subject, content, attachments, surveyPdf }: EmailParams): Promise { + /** + * @description 조사매물 pdf blob 및 buffer 생성 + */ + let surveyPdfBuffer: Buffer | null = null + if (surveyPdf) { + const resp = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/survey-sales/${surveyPdf.id}?isPdf=true`, { + method: 'GET', + headers: { + 'Content-Type': 'application/pdf', + }, + }) + const pdfBlob = await resp.blob() + surveyPdfBuffer = Buffer.from(await pdfBlob.arrayBuffer()) + } + const surveyPdfAttachment = surveyPdfBuffer ? [{ filename: '[HANASYS現地調査]' + surveyPdf?.filename + '.pdf', content: surveyPdfBuffer }] : [] + + /** + * @description SMTP 트랜스포터 생성 + */ const transporter = nodemailer.createTransport({ host: process.env.SMTP_HOST, port: Number(process.env.SMTP_PORT), @@ -22,21 +53,17 @@ export async function sendEmail({ from, to, cc, subject, content, attachments }: tls: { rejectUnauthorized: false }, }) - // Email options + /** + * @description 이메일 옵션 + */ const mailOptions = { from, to: Array.isArray(to) ? to.join(', ') : to, cc: cc ? (Array.isArray(cc) ? cc.join(', ') : cc) : undefined, subject, html: content, - attachments: attachments || [], + attachments: surveyPdf ? surveyPdfAttachment : attachments || [], } - try { - // Send email - await transporter.sendMail(mailOptions) - } catch (error) { - console.error('Error sending email:', error) - throw new Error('Failed to send email') - } + await transporter.sendMail(mailOptions) }