From 2db1357558e589eefe4fc868b16f3656f1da4a0f Mon Sep 17 00:00:00 2001 From: keyy1315 Date: Fri, 4 Jul 2025 16:11:35 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20PDF=20=EC=B2=A8=EB=B6=80=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EC=9D=84=20=EC=A7=80=EC=9B=90=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=A1=B0=EC=82=AC=EB=A7=A4=EB=AC=BC=20?= =?UTF-8?q?=EC=A0=9C=EC=B6=9C=20=ED=8C=9D=EC=97=85=20=EB=B0=8F=20mailer.ts?= =?UTF-8?q?=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../popup/SurveySaleSubmitPopup.tsx | 14 ++++-- src/libs/mailer.ts | 49 ++++++++++++++----- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/src/components/popup/SurveySaleSubmitPopup.tsx b/src/components/popup/SurveySaleSubmitPopup.tsx index 2093c38..7e8e7af 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,8 @@ export default function SurveySaleSubmitPopup() { const baseUpdate = { sender: session?.email ?? '', title: '[HANASYS現地調査] 調査物件が提出. (' + surveyDetail?.srlNo + ')', + srlNo: surveyDetail?.srlNo ?? null, + surveyId: surveyDetail?.id ?? null, } /** Admin 제출 폼 데이터 삽입 - 1차 판매점*/ if (session?.role === 'Admin') { @@ -106,8 +110,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 +142,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/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) } From 80b6e3644cde55befc137c6b5cd62ac3a7b4d53c Mon Sep 17 00:00:00 2001 From: keyy1315 Date: Fri, 4 Jul 2025 16:12:00 +0900 Subject: [PATCH 2/6] =?UTF-8?q?refactor:=20=EC=A1=B0=EC=82=AC=EB=A7=A4?= =?UTF-8?q?=EB=AC=BC=20=EC=83=9D=EC=84=B1/=EC=82=AD=EC=A0=9C/=EC=A0=9C?= =?UTF-8?q?=EC=B6=9C/=EC=88=98=EC=A0=95=20=EC=8B=9C=20=EB=B2=84=ED=8A=BC?= =?UTF-8?q?=20=EB=B9=84=ED=99=9C=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey-sale/detail/ButtonForm.tsx | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) 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 }) => (
-
From 927d13ad99131e9cf9bf12d8d0d2d2f72c5fdfe3 Mon Sep 17 00:00:00 2001 From: keyy1315 Date: Fri, 4 Jul 2025 16:12:13 +0900 Subject: [PATCH 3/6] =?UTF-8?q?refactor:=20=EB=A9=94=EC=9D=BC=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=B2=98=EB=A6=AC=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/qna/save/route.ts | 3 ++- src/app/api/qna/service.ts | 3 +++ src/components/inquiry/RegistForm.tsx | 8 +++----- src/hooks/useInquiry.ts | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) 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/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 문의사항 관련 기능을 제공하는 커스텀 훅 From a9866121e4437fc5c0c2bfe73b81d35dbcac1c3b Mon Sep 17 00:00:00 2001 From: keyy1315 Date: Fri, 4 Jul 2025 16:25:10 +0900 Subject: [PATCH 4/6] fix: add test mail --- src/libs/mailer.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libs/mailer.ts b/src/libs/mailer.ts index 10baec3..7d0c472 100644 --- a/src/libs/mailer.ts +++ b/src/libs/mailer.ts @@ -58,8 +58,10 @@ export async function sendEmail({ from, to, cc, subject, content, attachments, s */ const mailOptions = { from, - to: Array.isArray(to) ? to.join(', ') : to, - cc: cc ? (Array.isArray(cc) ? cc.join(', ') : cc) : undefined, + // to: Array.isArray(to) ? to.join(', ') : to, + // cc: cc ? (Array.isArray(cc) ? cc.join(', ') : cc) : undefined, + to: 'keyy1315@interplug.co.kr', + cc: 'keyy1315@interplug.co.kr', subject, html: content, attachments: surveyPdf ? surveyPdfAttachment : attachments || [], From ab376e0bae0e43c04443c54a534de953d2c02033 Mon Sep 17 00:00:00 2001 From: keyy1315 Date: Fri, 4 Jul 2025 16:40:21 +0900 Subject: [PATCH 5/6] fix: delete test mail --- src/libs/mailer.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/libs/mailer.ts b/src/libs/mailer.ts index 7d0c472..10baec3 100644 --- a/src/libs/mailer.ts +++ b/src/libs/mailer.ts @@ -58,10 +58,8 @@ export async function sendEmail({ from, to, cc, subject, content, attachments, s */ const mailOptions = { from, - // to: Array.isArray(to) ? to.join(', ') : to, - // cc: cc ? (Array.isArray(cc) ? cc.join(', ') : cc) : undefined, - to: 'keyy1315@interplug.co.kr', - cc: 'keyy1315@interplug.co.kr', + to: Array.isArray(to) ? to.join(', ') : to, + cc: cc ? (Array.isArray(cc) ? cc.join(', ') : cc) : undefined, subject, html: content, attachments: surveyPdf ? surveyPdfAttachment : attachments || [], From 4cb6e6b213ef5ce378f984293764c8be0919fee5 Mon Sep 17 00:00:00 2001 From: keyy1315 Date: Fri, 4 Jul 2025 16:45:26 +0900 Subject: [PATCH 6/6] fix: delete unused surveyId from email data --- src/components/popup/SurveySaleSubmitPopup.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/popup/SurveySaleSubmitPopup.tsx b/src/components/popup/SurveySaleSubmitPopup.tsx index 7e8e7af..db5cbe3 100644 --- a/src/components/popup/SurveySaleSubmitPopup.tsx +++ b/src/components/popup/SurveySaleSubmitPopup.tsx @@ -59,7 +59,6 @@ export default function SurveySaleSubmitPopup() { sender: session?.email ?? '', title: '[HANASYS現地調査] 調査物件が提出. (' + surveyDetail?.srlNo + ')', srlNo: surveyDetail?.srlNo ?? null, - surveyId: surveyDetail?.id ?? null, } /** Admin 제출 폼 데이터 삽입 - 1차 판매점*/ if (session?.role === 'Admin') {