diff --git a/backend-node/data/mail-sent/1d997eeb-3d61-427d-8b54-119d4372b9b3.json b/backend-node/data/mail-sent/1d997eeb-3d61-427d-8b54-119d4372b9b3.json deleted file mode 100644 index 683ad20c..00000000 --- a/backend-node/data/mail-sent/1d997eeb-3d61-427d-8b54-119d4372b9b3.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "id": "1d997eeb-3d61-427d-8b54-119d4372b9b3", - "sentAt": "2025-10-22T07:13:30.905Z", - "accountId": "account-1759310844272", - "accountName": "이희진", - "accountEmail": "hjlee@wace.me", - "to": [ - "zian9227@naver.com" - ], - "subject": "Fwd: ㄴ", - "htmlContent": "\r\n
\r\n

전달히야야양


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
전달된 메일:

보낸사람: \"이희진\"
날짜: 2025. 10. 22. 오후 12:58:15
제목: ㄴ
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

ㄴㅇㄹㄴㅇㄹㄴㅇㄹ

\r\n
\r\n ", - "status": "success", - "messageId": "", - "accepted": [ - "zian9227@naver.com" - ], - "rejected": [] -} \ No newline at end of file diff --git a/backend-node/data/mail-sent/1e492bb1-d069-4242-8cbf-9829b8f6c7e6.json b/backend-node/data/mail-sent/1e492bb1-d069-4242-8cbf-9829b8f6c7e6.json deleted file mode 100644 index eccdc063..00000000 --- a/backend-node/data/mail-sent/1e492bb1-d069-4242-8cbf-9829b8f6c7e6.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "id": "1e492bb1-d069-4242-8cbf-9829b8f6c7e6", - "sentAt": "2025-10-13T01:08:34.764Z", - "accountId": "account-1759310844272", - "accountName": "이희진", - "accountEmail": "hjlee@wace.me", - "to": [ - "zian9227@naver.com" - ], - "subject": "제목 없음", - "htmlContent": "\n\n\n\n \n \n\n\n \n \n \n \n
\n

ㄴㅇㄹㄴㅇㄹ

\n \"\"\n

ㄴㅇㄹ

ㄴㅇㄹ

\n
\n\n
\n \r\n
\r\n

ㄴㅇㄹ

\r\n
\r\n \n
\n \n\n", - "templateId": "template-1760315158387", - "templateName": "테스트2", - "attachments": [ - { - "filename": "스크린샷 2025-10-13 오전 10.00.06.png", - "originalName": "스크린샷 2025-10-13 오전 10.00.06.png", - "size": 0, - "path": "/app/uploads/mail-attachments/1760317712416-622369845.png", - "mimetype": "image/png" - } - ], - "status": "success", - "messageId": "", - "accepted": [ - "zian9227@naver.com" - ], - "rejected": [] -} \ No newline at end of file diff --git a/backend-node/data/mail-sent/2d848b19-26e1-45ad-8e2c-9205f1f01c87.json b/backend-node/data/mail-sent/2d848b19-26e1-45ad-8e2c-9205f1f01c87.json deleted file mode 100644 index a6fed281..00000000 --- a/backend-node/data/mail-sent/2d848b19-26e1-45ad-8e2c-9205f1f01c87.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "id": "2d848b19-26e1-45ad-8e2c-9205f1f01c87", - "sentAt": "2025-10-02T07:50:25.817Z", - "accountId": "account-1759310844272", - "accountName": "이희진", - "accountEmail": "hjlee@wace.me", - "to": [ - "zian9227@naver.com" - ], - "subject": "ㅣ;ㅏㅓ", - "htmlContent": "\r\n
\r\n

ㅓㅏㅣ

\r\n
\r\n ", - "attachments": [ - { - "filename": "test용 이미지33.jpg", - "originalName": "test용 이미지33.jpg", - "size": 0, - "path": "/app/uploads/mail-attachments/1759391422625-269479520_test____________________________33.jpg", - "mimetype": "image/jpeg" - }, - { - "filename": "UI_개선사항_문서.md", - "originalName": "UI_개선사항_문서.md", - "size": 0, - "path": "/app/uploads/mail-attachments/1759391422626-68453569_UI_______________________________________________.md", - "mimetype": "text/x-markdown" - }, - { - "filename": "test용 이미지2.png", - "originalName": "test용 이미지2.png", - "size": 0, - "path": "/app/uploads/mail-attachments/1759391422626-168170034_test____________________________2.png", - "mimetype": "image/png" - } - ], - "status": "success", - "messageId": "<9d5b8275-e059-3a71-a34a-dea800730aa3@wace.me>", - "accepted": [ - "zian9227@naver.com" - ], - "rejected": [] -} \ No newline at end of file diff --git a/backend-node/data/mail-sent/331d95d6-3a13-4657-bc75-ab0811712eb8.json b/backend-node/data/mail-sent/331d95d6-3a13-4657-bc75-ab0811712eb8.json deleted file mode 100644 index 5090fdd2..00000000 --- a/backend-node/data/mail-sent/331d95d6-3a13-4657-bc75-ab0811712eb8.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "id": "331d95d6-3a13-4657-bc75-ab0811712eb8", - "sentAt": "2025-10-22T07:18:18.240Z", - "accountId": "account-1759310844272", - "accountName": "이희진", - "accountEmail": "hjlee@wace.me", - "to": [ - "zian9227@naver.com" - ], - "subject": "ㅁㄴㅇㄹㅁㄴㅇㄹ", - "htmlContent": "\r\n
\r\n

ㅁㄴㅇㄹㅁㄴㅇㄹㄴㅇㄹㄴㅇㄹ

\r\n
\r\n ", - "status": "success", - "messageId": "", - "accepted": [ - "zian9227@naver.com" - ], - "rejected": [] -} \ No newline at end of file diff --git a/backend-node/data/mail-sent/34f7f149-ac97-442e-b595-02c990082f86.json b/backend-node/data/mail-sent/34f7f149-ac97-442e-b595-02c990082f86.json deleted file mode 100644 index 46b0b1b8..00000000 --- a/backend-node/data/mail-sent/34f7f149-ac97-442e-b595-02c990082f86.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "id": "34f7f149-ac97-442e-b595-02c990082f86", - "sentAt": "2025-10-13T01:04:08.560Z", - "accountId": "account-1759310844272", - "accountName": "이희진", - "accountEmail": "hjlee@wace.me", - "to": [ - "zian9227@naver.com" - ], - "subject": "제목 없음", - "htmlContent": "\n\n\n\n \n \n\n\n \n \n \n \n
\n

텍스트 영역 1

\n 버튼1\n
\n \"\"\n

텍스트 영역2

텍스트 영역3

\n
\n\n
\n \r\n
\r\n

선택메시지 영역

\r\n
\r\n \n
\n \n\n", - "templateId": "template-1760315158387", - "templateName": "테스트2", - "attachments": [ - { - "filename": "한글.txt", - "originalName": "한글.txt", - "size": 0, - "path": "/app/uploads/mail-attachments/1760317447824-27488793.txt", - "mimetype": "text/plain" - } - ], - "status": "success", - "messageId": "<1d7caa77-12f1-a791-a230-162826cf03ea@wace.me>", - "accepted": [ - "zian9227@naver.com" - ], - "rejected": [] -} \ No newline at end of file diff --git a/backend-node/data/mail-sent/37fce6a0-2301-431b-b573-82bdab9b8008.json b/backend-node/data/mail-sent/37fce6a0-2301-431b-b573-82bdab9b8008.json deleted file mode 100644 index d70b6897..00000000 --- a/backend-node/data/mail-sent/37fce6a0-2301-431b-b573-82bdab9b8008.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "id": "37fce6a0-2301-431b-b573-82bdab9b8008", - "sentAt": "2025-10-02T07:44:38.128Z", - "accountId": "account-1759310844272", - "accountName": "이희진", - "accountEmail": "hjlee@wace.me", - "to": [ - "zian9227@naver.com" - ], - "subject": "asd", - "htmlContent": "\r\n
\r\n

asd

\r\n
\r\n ", - "attachments": [ - { - "filename": "웨이스-임직원-프로파일-이희진.key", - "originalName": "웨이스-임직원-프로파일-이희진.key", - "size": 0, - "path": "/app/uploads/mail-attachments/1759391076653-58189058___________________-___________________________-___________________________-_____________________.key", - "mimetype": "application/x-iwork-keynote-sffkey" - }, - { - "filename": "웨이스-임직원-프로파일-이희진.pptx", - "originalName": "웨이스-임직원-프로파일-이희진.pptx", - "size": 0, - "path": "/app/uploads/mail-attachments/1759391076736-190208246___________________-___________________________-___________________________-_____________________.pptx", - "mimetype": "application/vnd.openxmlformats-officedocument.presentationml.presentation" - }, - { - "filename": "test용 이미지33.jpg", - "originalName": "test용 이미지33.jpg", - "size": 0, - "path": "/app/uploads/mail-attachments/1759391076738-240665795_test____________________________33.jpg", - "mimetype": "image/jpeg" - } - ], - "status": "success", - "messageId": "<796cb9a7-df62-31c4-ae6b-b42f383d82b4@wace.me>", - "accepted": [ - "zian9227@naver.com" - ], - "rejected": [] -} \ No newline at end of file diff --git a/backend-node/data/mail-sent/3f72cbab-b60e-45e7-ac8d-7e441bc2b900.json b/backend-node/data/mail-sent/3f72cbab-b60e-45e7-ac8d-7e441bc2b900.json deleted file mode 100644 index 05eb18c2..00000000 --- a/backend-node/data/mail-sent/3f72cbab-b60e-45e7-ac8d-7e441bc2b900.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "id": "3f72cbab-b60e-45e7-ac8d-7e441bc2b900", - "sentAt": "2025-10-13T01:34:19.363Z", - "accountId": "account-1759310844272", - "accountName": "이희진", - "accountEmail": "hjlee@wace.me", - "to": [ - "zian9227@naver.com" - ], - "subject": "테스트 템플릿이에용22", - "htmlContent": "\n\n\n\n \n \n\n\n \n \n \n \n
\n

안녕안녕하세요 이건 테스트용 템플릿입니다용22

\n \"\"\n

안녕하세용 [222]이안에 뭘 넣어보세용

여기에 뭘 또 입력해보세용[222] 안에 넣어도 돼요

\n
\n\n
\n \r\n
\r\n

ㄴㅇㄹㄴㅇㄹㄴㅇㄹ

\r\n
\r\n \n
\n \n\n", - "templateId": "template-1760315158387", - "templateName": "테스트2", - "attachments": [ - { - "filename": "blender study.docx", - "originalName": "blender study.docx", - "size": 0, - "path": "/app/uploads/mail-attachments/1760319257947-827879690.docx", - "mimetype": "application/vnd.openxmlformats-officedocument.wordprocessingml.document" - } - ], - "status": "success", - "messageId": "<5b3d9f82-8531-f427-c7f7-9446b4f19da4@wace.me>", - "accepted": [ - "zian9227@naver.com" - ], - "rejected": [] -} \ No newline at end of file diff --git a/backend-node/data/mail-sent/43466fc8-56e8-44a0-875c-dec2c3c8eb78.json b/backend-node/data/mail-sent/43466fc8-56e8-44a0-875c-dec2c3c8eb78.json new file mode 100644 index 00000000..ea3b568f --- /dev/null +++ b/backend-node/data/mail-sent/43466fc8-56e8-44a0-875c-dec2c3c8eb78.json @@ -0,0 +1,20 @@ +{ + "id": "43466fc8-56e8-44a0-875c-dec2c3c8eb78", + "sentAt": "2025-11-28T02:34:02.239Z", + "accountId": "account-1759310844272", + "accountName": "이희진", + "accountEmail": "hjlee@wace.me", + "to": [ + "zian9227@naver.com" + ], + "subject": "임의로 설정한 제목", + "htmlContent": "\n\n\n\n \n \n\n\n \n \n \n \n
\n\n
\n \n \n \n \n \n
\n \n (주)웨이스\n \n 2025. 11. 28.\n
\n
\n
\n naver\n
\n \"\"\n
\n
\n
(주)웨이스
\n \n
\n 대표: 이희진\n \n \n
\n \n
주소주소
\n \n
\n Tel: 전화번호 01010101011010\n | \n Email: 이메일이메일\n
\n \n
© 2025 All rights reserved.
\n
\n \n
\n \n \n \n \n \n \n \n \n
항목내용
\n
\n \n
\n
안내
\n
안내를 합시다 합시다 합시다
\n
\n \n
\n \n
    \n
  1. 첫 번째 항목
  2. 두번째항목
  3. \n
\n
\n \n
\n \n
    \n
  1. 첫 번째 항목
  2. \n
\n
\n \n
\n \n
    \n
  1. 첫 번째 항목
  2. \n
\n
\n \n
\n \n
    \n
  1. 첫 번째 항목
  2. \n
\n
\n \n
\n\n\n", + "templateId": "template-1764296982213", + "templateName": "제목 있음", + "status": "success", + "messageId": "<78b63521-2648-f6eb-eeba-efdeebce8459@wace.me>", + "accepted": [ + "zian9227@naver.com" + ], + "rejected": [] +} \ No newline at end of file diff --git a/backend-node/data/mail-sent/449d9951-51e8-4e81-ada4-e73aed8ff60e.json b/backend-node/data/mail-sent/449d9951-51e8-4e81-ada4-e73aed8ff60e.json deleted file mode 100644 index 29ec634e..00000000 --- a/backend-node/data/mail-sent/449d9951-51e8-4e81-ada4-e73aed8ff60e.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "449d9951-51e8-4e81-ada4-e73aed8ff60e", - "sentAt": "2025-10-13T01:29:25.975Z", - "accountId": "account-1759310844272", - "accountName": "이희진", - "accountEmail": "hjlee@wace.me", - "to": [ - "zian9227@naver.com" - ], - "subject": "테스트 템플릿이에용", - "htmlContent": "\n\n\n\n \n \n\n\n \n \n \n \n
\n
안녕안녕하세요 이건 테스트용 템플릿입니다용
\n \"\"\n

안녕하세용 [뭘 넣은 결과 입니당]이안에 뭘 넣어보세용

여기에 뭘 또 입력해보세용[안에 뭘 넣은 결과입니다.] 안에 넣어도 돼요

\n
\n\n\n", - "templateId": "template-1760315158387", - "templateName": "테스트2", - "status": "success", - "messageId": "<5d52accb-777b-b6c2-aab7-1a2f7b7754ab@wace.me>", - "accepted": [ - "zian9227@naver.com" - ], - "rejected": [] -} \ No newline at end of file diff --git a/backend-node/data/mail-sent/6dd3673a-f510-4ba9-9634-0b391f925230.json b/backend-node/data/mail-sent/6dd3673a-f510-4ba9-9634-0b391f925230.json deleted file mode 100644 index ee094c49..00000000 --- a/backend-node/data/mail-sent/6dd3673a-f510-4ba9-9634-0b391f925230.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "id": "6dd3673a-f510-4ba9-9634-0b391f925230", - "sentAt": "2025-10-13T01:01:55.097Z", - "accountId": "account-1759310844272", - "accountName": "이희진", - "accountEmail": "hjlee@wace.me", - "to": [ - "zian9227@naver.com" - ], - "subject": "테스트용입니당.", - "htmlContent": "\n\n\n\n \n \n\n\n \n \n \n \n
\n \n \n \n \n
\n

텍스트 영역 1

\n 버튼1\n
\n \"\"\n

텍스트 영역2

텍스트 영역3

\n
\n
\n\n
\n \r\n
\r\n

이건 저장이 안되는군

\r\n
\r\n \n
\n \n\n", - "templateId": "template-1760315158387", - "templateName": "테스트2", - "attachments": [ - { - "filename": "한글-분석.txt", - "originalName": "한글-분석.txt", - "size": 0, - "path": "/app/uploads/mail-attachments/1760317313641-761345104.txt", - "mimetype": "text/plain" - } - ], - "status": "success", - "messageId": "", - "accepted": [ - "zian9227@naver.com" - ], - "rejected": [] -} \ No newline at end of file diff --git a/backend-node/data/mail-sent/84ee9619-49ff-4f61-a7fa-0bb0b0b7199a.json b/backend-node/data/mail-sent/84ee9619-49ff-4f61-a7fa-0bb0b0b7199a.json deleted file mode 100644 index 37317a6a..00000000 --- a/backend-node/data/mail-sent/84ee9619-49ff-4f61-a7fa-0bb0b0b7199a.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "id": "84ee9619-49ff-4f61-a7fa-0bb0b0b7199a", - "sentAt": "2025-10-22T04:27:51.044Z", - "accountId": "account-1759310844272", - "accountName": "이희진", - "accountEmail": "hjlee@wace.me", - "to": [ - "\"이희진\" " - ], - "subject": "Re: ㅅㄷㄴㅅ", - "htmlContent": "\r\n
\r\n

야야야야야야야야ㅑㅇ야ㅑㅇ

\r\n
\r\n

\r\n
\r\n

보낸 사람: \"이희진\"

\r\n

날짜: 2025. 10. 22. 오후 1:03:03

\r\n

제목: ㅅㄷㄴㅅ

\r\n
\r\n undefined\r\n
\r\n ", - "status": "success", - "messageId": "<5fa451ff-7d29-7da4-ce56-ca7391c147af@wace.me>", - "accepted": [ - "zian9227@naver.com" - ], - "rejected": [] -} \ No newline at end of file diff --git a/backend-node/data/mail-sent/89a32ace-f39b-44fa-b614-c65d96548f92.json b/backend-node/data/mail-sent/89a32ace-f39b-44fa-b614-c65d96548f92.json deleted file mode 100644 index 4ac647c7..00000000 --- a/backend-node/data/mail-sent/89a32ace-f39b-44fa-b614-c65d96548f92.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "id": "89a32ace-f39b-44fa-b614-c65d96548f92", - "sentAt": "2025-10-22T03:49:48.461Z", - "accountId": "account-1759310844272", - "accountName": "이희진", - "accountEmail": "hjlee@wace.me", - "to": [ - "zian9227@naver.com" - ], - "subject": "Fwd: 기상청 API허브 회원가입 인증번호", - "htmlContent": "\r\n
\r\n






---------- 전달된 메시지 ----------


보낸 사람: \"기상청 API허브\"


날짜: 2025. 10. 13. 오후 4:26:45


제목: 기상청 API허브 회원가입 인증번호




undefined

\r\n
\r\n ", - "status": "success", - "messageId": "<9b36ce56-4ef1-cf0c-1f39-2c73bcb521da@wace.me>", - "accepted": [ - "zian9227@naver.com" - ], - "rejected": [] -} \ No newline at end of file diff --git a/backend-node/data/mail-sent/9eab902e-f77b-424f-ada4-0ea8709b36bf.json b/backend-node/data/mail-sent/9eab902e-f77b-424f-ada4-0ea8709b36bf.json deleted file mode 100644 index ed2e4b14..00000000 --- a/backend-node/data/mail-sent/9eab902e-f77b-424f-ada4-0ea8709b36bf.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "id": "9eab902e-f77b-424f-ada4-0ea8709b36bf", - "sentAt": "2025-10-13T00:53:55.193Z", - "accountId": "account-1759310844272", - "accountName": "이희진", - "accountEmail": "hjlee@wace.me", - "to": [ - "zian9227@naver.com" - ], - "subject": "제목 없음", - "htmlContent": "

텍스트를 입력하세요...

\n 버튼\n
\n \"\"\n

텍스트를 입력하세요...

텍스트를 입력하세요...

\n
\n \r\n
\r\n

어덯게 나오는지 봅시다 추가메시지 영역이빈다.

\r\n
\r\n \n
\n
", - "templateId": "template-1760315158387", - "templateName": "테스트2", - "attachments": [ - { - "filename": "한글.txt", - "originalName": "한글.txt", - "size": 0, - "path": "/app/uploads/mail-attachments/1760316833254-789302611.txt", - "mimetype": "text/plain" - } - ], - "status": "success", - "messageId": "<3d0bef10-2e58-fd63-b175-c1f499af0102@wace.me>", - "accepted": [ - "zian9227@naver.com" - ], - "rejected": [] -} \ No newline at end of file diff --git a/backend-node/data/mail-sent/a1ca39ad-4467-44e0-963a-fba5037c8896.json b/backend-node/data/mail-sent/a1ca39ad-4467-44e0-963a-fba5037c8896.json deleted file mode 100644 index 31492a08..00000000 --- a/backend-node/data/mail-sent/a1ca39ad-4467-44e0-963a-fba5037c8896.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "id": "a1ca39ad-4467-44e0-963a-fba5037c8896", - "sentAt": "2025-10-02T08:22:14.721Z", - "accountId": "account-1759310844272", - "accountName": "이희진", - "accountEmail": "hjlee@wace.me", - "to": [ - "zian9227@naver.com" - ], - "subject": "ㅁㄴㅇㄹㅁㄴㅇㄹㅁㄴㅇㄹㅁㄴ", - "htmlContent": "\r\n
\r\n

ㅁㄴㅇㄹㅁㄴㅇㄹㄴㅁㅇㄹ

\r\n
\r\n ", - "attachments": [ - { - "filename": "test용 이미지33.jpg", - "originalName": "test용 이미지33.jpg", - "size": 0, - "path": "/app/uploads/mail-attachments/1759393332207-791945862_test____________________________33.jpg", - "mimetype": "image/jpeg" - }, - { - "filename": "UI_개선사항_문서.md", - "originalName": "UI_개선사항_문서.md", - "size": 0, - "path": "/app/uploads/mail-attachments/1759393332208-660280542_UI_______________________________________________.md", - "mimetype": "text/x-markdown" - }, - { - "filename": "test용 이미지2.png", - "originalName": "test용 이미지2.png", - "size": 0, - "path": "/app/uploads/mail-attachments/1759393332208-149486455_test____________________________2.png", - "mimetype": "image/png" - } - ], - "status": "success", - "messageId": "", - "accepted": [ - "zian9227@naver.com" - ], - "rejected": [] -} \ No newline at end of file diff --git a/backend-node/data/mail-sent/a3a9aab1-4334-46bd-bf50-b867305f66c0.json b/backend-node/data/mail-sent/a3a9aab1-4334-46bd-bf50-b867305f66c0.json deleted file mode 100644 index 1435f837..00000000 --- a/backend-node/data/mail-sent/a3a9aab1-4334-46bd-bf50-b867305f66c0.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "id": "a3a9aab1-4334-46bd-bf50-b867305f66c0", - "sentAt": "2025-10-02T08:41:42.086Z", - "accountId": "account-1759310844272", - "accountName": "이희진", - "accountEmail": "hjlee@wace.me", - "to": [ - "zian9227@naver.com" - ], - "subject": "한글테스트", - "htmlContent": "\r\n
\r\n

ㅁㄴㅇㄹ

\r\n
\r\n ", - "attachments": [ - { - "filename": "UI_개선사항_문서.md", - "originalName": "UI_개선사항_문서.md", - "size": 0, - "path": "/app/uploads/mail-attachments/1759394500462-50127394_UI_______________________________________________.md", - "mimetype": "text/x-markdown" - }, - { - "filename": "test용 이미지33.jpg", - "originalName": "test용 이미지33.jpg", - "size": 0, - "path": "/app/uploads/mail-attachments/1759394500463-68744474_test____________________________33.jpg", - "mimetype": "image/jpeg" - }, - { - "filename": "test용 이미지2.png", - "originalName": "test용 이미지2.png", - "size": 0, - "path": "/app/uploads/mail-attachments/1759394500463-464487722_test____________________________2.png", - "mimetype": "image/png" - } - ], - "status": "success", - "messageId": "<2dbfbf64-69c2-a83d-6bb7-515e4e654628@wace.me>", - "accepted": [ - "zian9227@naver.com" - ], - "rejected": [] -} \ No newline at end of file diff --git a/backend-node/data/mail-sent/a638f7d0-ee31-47fa-9f72-de66ef31ea44.json b/backend-node/data/mail-sent/a638f7d0-ee31-47fa-9f72-de66ef31ea44.json deleted file mode 100644 index 5cf165c3..00000000 --- a/backend-node/data/mail-sent/a638f7d0-ee31-47fa-9f72-de66ef31ea44.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "id": "a638f7d0-ee31-47fa-9f72-de66ef31ea44", - "sentAt": "2025-10-22T07:21:13.723Z", - "accountId": "account-1759310844272", - "accountName": "이희진", - "accountEmail": "hjlee@wace.me", - "to": [ - "zian9227@naver.com" - ], - "subject": "ㄹㅇㄴㅁㄹㅇㄴㅁ", - "htmlContent": "\r\n
\r\n

ㄹㅇㄴㅁㄹㅇㄴㅁㅇㄹㅇㄴㅁ

\r\n
\r\n ", - "status": "success", - "messageId": "<5ea07d02-78bf-a655-8289-bcbd8eaf7741@wace.me>", - "accepted": [ - "zian9227@naver.com" - ], - "rejected": [] -} \ No newline at end of file diff --git a/backend-node/data/mail-sent/b1d8f458-076c-4c44-982e-d2f46dcd4b03.json b/backend-node/data/mail-sent/b1d8f458-076c-4c44-982e-d2f46dcd4b03.json deleted file mode 100644 index 8f8d5059..00000000 --- a/backend-node/data/mail-sent/b1d8f458-076c-4c44-982e-d2f46dcd4b03.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "id": "b1d8f458-076c-4c44-982e-d2f46dcd4b03", - "sentAt": "2025-10-02T08:57:48.412Z", - "accountId": "account-1759310844272", - "accountName": "이희진", - "accountEmail": "hjlee@wace.me", - "to": [ - "zian9227@naver.com" - ], - "subject": "ㅁㄴㅇㄹ", - "htmlContent": "\r\n
\r\n

ㅁㄴㅇㄹ

\r\n
\r\n ", - "attachments": [ - { - "filename": "웨이스-임직원-프로파일-이희진.key", - "originalName": "웨이스-임직원-프로파일-이희진.key", - "size": 0, - "path": "/app/uploads/mail-attachments/1759395465488-120933172.key", - "mimetype": "application/x-iwork-keynote-sffkey" - }, - { - "filename": "UI_개선사항_문서.md", - "originalName": "UI_개선사항_문서.md", - "size": 0, - "path": "/app/uploads/mail-attachments/1759395465566-306126854.md", - "mimetype": "text/x-markdown" - }, - { - "filename": "test용 이미지33.jpg", - "originalName": "test용 이미지33.jpg", - "size": 0, - "path": "/app/uploads/mail-attachments/1759395465566-412984398.jpg", - "mimetype": "image/jpeg" - }, - { - "filename": "test용 이미지2.png", - "originalName": "test용 이미지2.png", - "size": 0, - "path": "/app/uploads/mail-attachments/1759395465567-143883587.png", - "mimetype": "image/png" - } - ], - "status": "success", - "messageId": "", - "accepted": [ - "zian9227@naver.com" - ], - "rejected": [] -} \ No newline at end of file diff --git a/backend-node/data/mail-sent/b75d0b2b-7d8a-461b-b854-2bebdef959e8.json b/backend-node/data/mail-sent/b75d0b2b-7d8a-461b-b854-2bebdef959e8.json deleted file mode 100644 index dbec91a5..00000000 --- a/backend-node/data/mail-sent/b75d0b2b-7d8a-461b-b854-2bebdef959e8.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "id": "b75d0b2b-7d8a-461b-b854-2bebdef959e8", - "sentAt": "2025-10-02T08:49:30.356Z", - "accountId": "account-1759310844272", - "accountName": "이희진", - "accountEmail": "hjlee@wace.me", - "to": [ - "zian9227@naver.com" - ], - "subject": "한글2", - "htmlContent": "\r\n
\r\n

ㅁㄴㅇㄹ

\r\n
\r\n ", - "attachments": [ - { - "filename": "UI_áá¢áá¥á«áá¡áá¡á¼_áá®á«áá¥.md", - "originalName": "UI_áá¢áá¥á«áá¡áá¡á¼_áá®á«áá¥.md", - "size": 0, - "path": "/app/uploads/mail-attachments/1759394969516-74008147_UI__________________________.md", - "mimetype": "text/x-markdown" - }, - { - "filename": "testáá­á¼ ááµááµááµ33.jpg", - "originalName": "testáá­á¼ ááµááµááµ33.jpg", - "size": 0, - "path": "/app/uploads/mail-attachments/1759394969516-530544653_test_______________33.jpg", - "mimetype": "image/jpeg" - }, - { - "filename": "testáá­á¼ ááµááµááµ2.png", - "originalName": "testáá­á¼ ááµááµááµ2.png", - "size": 0, - "path": "/app/uploads/mail-attachments/1759394969517-260831218_test_______________2.png", - "mimetype": "image/png" - } - ], - "status": "success", - "messageId": "<80a431a1-bb4d-31b5-2564-93f8c2539fd4@wace.me>", - "accepted": [ - "zian9227@naver.com" - ], - "rejected": [] -} \ No newline at end of file diff --git a/backend-node/data/mail-sent/ccdd8961-1b3f-4b88-b838-51d6ed8f1601.json b/backend-node/data/mail-sent/ccdd8961-1b3f-4b88-b838-51d6ed8f1601.json deleted file mode 100644 index d2d4c424..00000000 --- a/backend-node/data/mail-sent/ccdd8961-1b3f-4b88-b838-51d6ed8f1601.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "id": "ccdd8961-1b3f-4b88-b838-51d6ed8f1601", - "sentAt": "2025-10-02T08:47:03.481Z", - "accountId": "account-1759310844272", - "accountName": "이희진", - "accountEmail": "hjlee@wace.me", - "to": [ - "zian9227@naver.com" - ], - "subject": "한글테스트222", - "htmlContent": "\r\n
\r\n

2

\r\n
\r\n ", - "attachments": [ - { - "filename": "UI_áá¢áá¥á«áá¡áá¡á¼_áá®á«áá¥.md", - "originalName": "UI_áá¢áá¥á«áá¡áá¡á¼_áá®á«áá¥.md", - "size": 0, - "path": "/app/uploads/mail-attachments/1759394821751-229305880_UI__________________________.md", - "mimetype": "text/x-markdown" - }, - { - "filename": "testáá­á¼ ááµááµááµ33.jpg", - "originalName": "testáá­á¼ ááµááµááµ33.jpg", - "size": 0, - "path": "/app/uploads/mail-attachments/1759394821751-335146895_test_______________33.jpg", - "mimetype": "image/jpeg" - }, - { - "filename": "testáá­á¼ ááµááµááµ2.png", - "originalName": "testáá­á¼ ááµááµááµ2.png", - "size": 0, - "path": "/app/uploads/mail-attachments/1759394821753-911076131_test_______________2.png", - "mimetype": "image/png" - } - ], - "status": "success", - "messageId": "<69519c70-a5cd-421d-9976-8c7014d69b39@wace.me>", - "accepted": [ - "zian9227@naver.com" - ], - "rejected": [] -} \ No newline at end of file diff --git a/backend-node/data/mail-sent/e2801ec2-6219-4c3c-83b4-8a6834569488.json b/backend-node/data/mail-sent/e2801ec2-6219-4c3c-83b4-8a6834569488.json deleted file mode 100644 index 1a388699..00000000 --- a/backend-node/data/mail-sent/e2801ec2-6219-4c3c-83b4-8a6834569488.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "id": "e2801ec2-6219-4c3c-83b4-8a6834569488", - "sentAt": "2025-10-13T00:59:46.729Z", - "accountId": "account-1759310844272", - "accountName": "이희진", - "accountEmail": "hjlee@wace.me", - "to": [ - "zian9227@naver.com" - ], - "subject": "제목 없음", - "htmlContent": "

텍스트 영역 1

\n 버튼1\n
\n \"\"\n

텍스트 영역2

텍스트 영역3

\n
\n \r\n
\r\n

추가메시지 영역

\r\n
\r\n \n
\n
", - "templateId": "template-1760315158387", - "templateName": "테스트2", - "attachments": [ - { - "filename": "한글.txt", - "originalName": "한글.txt", - "size": 0, - "path": "/app/uploads/mail-attachments/1760317184642-745285906.txt", - "mimetype": "text/plain" - } - ], - "status": "success", - "messageId": "<1e0abffb-a6cc-8312-d8b4-31c33cb72aa7@wace.me>", - "accepted": [ - "zian9227@naver.com" - ], - "rejected": [] -} \ No newline at end of file diff --git a/backend-node/data/mail-sent/e93848a8-6901-44c4-b4db-27c8d2aeb8dd.json b/backend-node/data/mail-sent/e93848a8-6901-44c4-b4db-27c8d2aeb8dd.json deleted file mode 100644 index 74c8212f..00000000 --- a/backend-node/data/mail-sent/e93848a8-6901-44c4-b4db-27c8d2aeb8dd.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "id": "e93848a8-6901-44c4-b4db-27c8d2aeb8dd", - "sentAt": "2025-10-22T04:28:42.686Z", - "accountId": "account-1759310844272", - "accountName": "이희진", - "accountEmail": "hjlee@wace.me", - "to": [ - "\"권은아\" " - ], - "subject": "Re: 매우 졸린 오후예요", - "htmlContent": "\r\n
\r\n

호홋 답장 기능을 구현했다죵
얼른 퇴근하고 싪네여

\r\n
\r\n

\r\n
\r\n

보낸 사람: \"권은아\"

\r\n

날짜: 2025. 10. 22. 오후 1:10:37

\r\n

제목: 매우 졸린 오후예요

\r\n
\r\n undefined\r\n
\r\n ", - "attachments": [ - { - "filename": "test용 이미지2.png", - "originalName": "test용 이미지2.png", - "size": 0, - "path": "/app/uploads/mail-attachments/1761107318152-717716316.png", - "mimetype": "image/png" - } - ], - "status": "success", - "messageId": "<19981423-259b-0a50-e76d-23c860692c16@wace.me>", - "accepted": [ - "chna8137s@gmail.com" - ], - "rejected": [] -} \ No newline at end of file diff --git a/backend-node/data/mail-sent/ee0d162c-48ad-4c00-8c56-ade80be4503f.json b/backend-node/data/mail-sent/ee0d162c-48ad-4c00-8c56-ade80be4503f.json deleted file mode 100644 index 45c6a1eb..00000000 --- a/backend-node/data/mail-sent/ee0d162c-48ad-4c00-8c56-ade80be4503f.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "id": "ee0d162c-48ad-4c00-8c56-ade80be4503f", - "sentAt": "2025-10-02T08:48:29.740Z", - "accountId": "account-1759310844272", - "accountName": "이희진", - "accountEmail": "hjlee@wace.me", - "to": [ - "zian9227@naver.com" - ], - "subject": "한글한글", - "htmlContent": "\r\n
\r\n

ㅁㄴㅇㄹㅁㄴㅇㄹㅁㄴㅇㄹ

\r\n
\r\n ", - "attachments": [ - { - "filename": "UI_áá¢áá¥á«áá¡áá¡á¼_áá®á«áá¥.md", - "originalName": "UI_áá¢áá¥á«áá¡áá¡á¼_áá®á«áá¥.md", - "size": 0, - "path": "/app/uploads/mail-attachments/1759394908877-38147683_UI__________________________.md", - "mimetype": "text/x-markdown" - }, - { - "filename": "testáá­á¼ ááµááµááµ33.jpg", - "originalName": "testáá­á¼ ááµááµááµ33.jpg", - "size": 0, - "path": "/app/uploads/mail-attachments/1759394908879-80461065_test_______________33.jpg", - "mimetype": "image/jpeg" - }, - { - "filename": "testáá­á¼ ááµááµááµ2.png", - "originalName": "testáá­á¼ ááµááµááµ2.png", - "size": 0, - "path": "/app/uploads/mail-attachments/1759394908880-475630926_test_______________2.png", - "mimetype": "image/png" - } - ], - "status": "success", - "messageId": "<96205714-1a6b-adb7-7ae5-0e1e3fcb700b@wace.me>", - "accepted": [ - "zian9227@naver.com" - ], - "rejected": [] -} \ No newline at end of file diff --git a/backend-node/data/mail-sent/fc26aba3-6b6e-47ba-91e8-609ae25e0e7d.json b/backend-node/data/mail-sent/fc26aba3-6b6e-47ba-91e8-609ae25e0e7d.json deleted file mode 100644 index f64daf8c..00000000 --- a/backend-node/data/mail-sent/fc26aba3-6b6e-47ba-91e8-609ae25e0e7d.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "id": "fc26aba3-6b6e-47ba-91e8-609ae25e0e7d", - "sentAt": "2025-10-13T00:21:51.799Z", - "accountId": "account-1759310844272", - "accountName": "이희진", - "accountEmail": "hjlee@wace.me", - "to": [ - "zian9227@naver.com" - ], - "subject": "test용입니다.", - "htmlContent": "\r\n
\r\n

ㅁㄴㅇㄹ

\r\n
\r\n ", - "templateId": "template-1759302346758", - "templateName": "test", - "attachments": [ - { - "filename": "웨이스-임직원-프로파일-이희진.key", - "originalName": "웨이스-임직원-프로파일-이희진.key", - "size": 0, - "path": "/app/uploads/mail-attachments/1760314910154-84512253.key", - "mimetype": "application/x-iwork-keynote-sffkey" - } - ], - "status": "success", - "messageId": "", - "accepted": [ - "zian9227@naver.com" - ], - "rejected": [] -} \ No newline at end of file diff --git a/backend-node/data/mail-sent/fcea6149-a098-4212-aa00-baef0cc083d6.json b/backend-node/data/mail-sent/fcea6149-a098-4212-aa00-baef0cc083d6.json deleted file mode 100644 index efd9a0c0..00000000 --- a/backend-node/data/mail-sent/fcea6149-a098-4212-aa00-baef0cc083d6.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "id": "fcea6149-a098-4212-aa00-baef0cc083d6", - "sentAt": "2025-10-22T04:24:54.126Z", - "accountId": "account-1759310844272", - "accountName": "이희진", - "accountEmail": "hjlee@wace.me", - "to": [ - "\"DHS\" " - ], - "subject": "Re: 안녕하세여", - "htmlContent": "\r\n
\r\n

어떻게 가는지 궁금한데 이따가 화면 보여주세영

\r\n
\r\n

\r\n
\r\n

보낸 사람: \"DHS\"

\r\n

날짜: 2025. 10. 22. 오후 1:09:49

\r\n

제목: 안녕하세여

\r\n
\r\n undefined\r\n
\r\n ", - "status": "success", - "messageId": "", - "accepted": [ - "ddhhss0603@gmail.com" - ], - "rejected": [] -} \ No newline at end of file diff --git a/backend-node/src/routes/externalRestApiConnectionRoutes.ts b/backend-node/src/routes/externalRestApiConnectionRoutes.ts index a789b218..48813575 100644 --- a/backend-node/src/routes/externalRestApiConnectionRoutes.ts +++ b/backend-node/src/routes/externalRestApiConnectionRoutes.ts @@ -267,4 +267,46 @@ router.post( } ); +/** + * POST /api/external-rest-api-connections/:id/fetch + * REST API 데이터 조회 (화면관리용 프록시) + */ +router.post( + "/:id/fetch", + authenticateToken, + async (req: AuthenticatedRequest, res: Response) => { + try { + const id = parseInt(req.params.id); + + if (isNaN(id)) { + return res.status(400).json({ + success: false, + message: "유효하지 않은 ID입니다.", + }); + } + + const { endpoint, jsonPath } = req.body; + const userCompanyCode = req.user?.companyCode; + + logger.info(`REST API 데이터 조회 요청: 연결 ID=${id}, endpoint=${endpoint}, jsonPath=${jsonPath}`); + + const result = await ExternalRestApiConnectionService.fetchData( + id, + endpoint, + jsonPath, + userCompanyCode + ); + + return res.status(result.success ? 200 : 400).json(result); + } catch (error) { + logger.error("REST API 데이터 조회 오류:", error); + return res.status(500).json({ + success: false, + message: "서버 내부 오류가 발생했습니다.", + error: error instanceof Error ? error.message : "알 수 없는 오류", + }); + } + } +); + export default router; diff --git a/backend-node/src/services/externalRestApiConnectionService.ts b/backend-node/src/services/externalRestApiConnectionService.ts index 668c07ae..36f3a7e2 100644 --- a/backend-node/src/services/externalRestApiConnectionService.ts +++ b/backend-node/src/services/externalRestApiConnectionService.ts @@ -166,6 +166,9 @@ export class ExternalRestApiConnectionService { ? this.decryptSensitiveData(connection.auth_config) : null; + // 디버깅: 조회된 연결 정보 로깅 + logger.info(`REST API 연결 조회 결과 (ID: ${id}): connection_name=${connection.connection_name}, default_method=${connection.default_method}, endpoint_path=${connection.endpoint_path}`); + return { success: true, data: connection, @@ -227,6 +230,15 @@ export class ExternalRestApiConnectionService { data.created_by || "system", ]; + // 디버깅: 저장하려는 데이터 로깅 + logger.info(`REST API 연결 생성 요청 데이터:`, { + connection_name: data.connection_name, + default_method: data.default_method, + endpoint_path: data.endpoint_path, + base_url: data.base_url, + default_body: data.default_body ? "있음" : "없음", + }); + const result: QueryResult = await pool.query(query, params); logger.info(`REST API 연결 생성 성공: ${data.connection_name}`); @@ -316,12 +328,14 @@ export class ExternalRestApiConnectionService { updateFields.push(`default_method = $${paramIndex}`); params.push(data.default_method); paramIndex++; + logger.info(`수정 요청 - default_method: ${data.default_method}`); } if (data.default_body !== undefined) { updateFields.push(`default_request_body = $${paramIndex}`); - params.push(data.default_body); + params.push(data.default_body); // null이면 DB에서 NULL로 저장됨 paramIndex++; + logger.info(`수정 요청 - default_body: ${data.default_body ? "있음" : "삭제(null)"}`); } if (data.auth_type !== undefined) { @@ -870,6 +884,166 @@ export class ExternalRestApiConnectionService { return decrypted; } + /** + * REST API 데이터 조회 (화면관리용 프록시) + * 저장된 연결 정보를 사용하여 외부 REST API를 호출하고 데이터를 반환 + */ + static async fetchData( + connectionId: number, + endpoint?: string, + jsonPath?: string, + userCompanyCode?: string + ): Promise> { + try { + // 연결 정보 조회 + const connectionResult = await this.getConnectionById(connectionId, userCompanyCode); + + if (!connectionResult.success || !connectionResult.data) { + return { + success: false, + message: "REST API 연결을 찾을 수 없습니다.", + error: { + code: "CONNECTION_NOT_FOUND", + details: `연결 ID ${connectionId}를 찾을 수 없습니다.`, + }, + }; + } + + const connection = connectionResult.data; + + // 비활성화된 연결인지 확인 + if (connection.is_active !== "Y") { + return { + success: false, + message: "비활성화된 REST API 연결입니다.", + error: { + code: "CONNECTION_INACTIVE", + details: "연결이 비활성화 상태입니다.", + }, + }; + } + + // 엔드포인트 결정 (파라미터 > 저장된 값) + const effectiveEndpoint = endpoint || connection.endpoint_path || ""; + + // API 호출을 위한 테스트 요청 생성 + const testRequest: RestApiTestRequest = { + id: connection.id, + base_url: connection.base_url, + endpoint: effectiveEndpoint, + method: (connection.default_method as any) || "GET", + headers: connection.default_headers, + body: connection.default_body, + auth_type: connection.auth_type, + auth_config: connection.auth_config, + timeout: connection.timeout, + }; + + // API 호출 + const result = await this.testConnection(testRequest, connection.company_code); + + if (!result.success) { + return { + success: false, + message: result.message || "REST API 호출에 실패했습니다.", + error: { + code: "API_CALL_FAILED", + details: result.error_details, + }, + }; + } + + // 응답 데이터에서 jsonPath로 데이터 추출 + let extractedData = result.response_data; + + logger.info(`REST API 원본 응답 데이터 타입: ${typeof result.response_data}`); + logger.info(`REST API 원본 응답 데이터 (일부): ${JSON.stringify(result.response_data)?.substring(0, 500)}`); + + if (jsonPath && result.response_data) { + try { + // jsonPath로 데이터 추출 (예: "data", "data.items", "result.list") + const pathParts = jsonPath.split("."); + logger.info(`JSON Path 파싱: ${jsonPath} -> [${pathParts.join(", ")}]`); + + for (const part of pathParts) { + if (extractedData && typeof extractedData === "object") { + extractedData = (extractedData as any)[part]; + logger.info(`JSON Path '${part}' 추출 결과 타입: ${typeof extractedData}, 배열?: ${Array.isArray(extractedData)}`); + } else { + logger.warn(`JSON Path '${part}' 추출 실패: extractedData가 객체가 아님`); + break; + } + } + } catch (pathError) { + logger.warn(`JSON Path 추출 실패: ${jsonPath}`, pathError); + // 추출 실패 시 원본 데이터 반환 + extractedData = result.response_data; + } + } + + // 데이터가 배열이 아닌 경우 배열로 변환 + // null이나 undefined인 경우 빈 배열로 처리 + let dataArray: any[] = []; + if (extractedData === null || extractedData === undefined) { + logger.warn("추출된 데이터가 null/undefined입니다. 원본 응답 데이터를 사용합니다."); + // jsonPath 추출 실패 시 원본 데이터에서 직접 컬럼 추출 시도 + if (result.response_data && typeof result.response_data === "object") { + dataArray = Array.isArray(result.response_data) ? result.response_data : [result.response_data]; + } + } else { + dataArray = Array.isArray(extractedData) ? extractedData : [extractedData]; + } + + logger.info(`최종 데이터 배열 길이: ${dataArray.length}`); + if (dataArray.length > 0) { + logger.info(`첫 번째 데이터 항목: ${JSON.stringify(dataArray[0])?.substring(0, 300)}`); + } + + // 컬럼 정보 추출 (첫 번째 유효한 데이터 기준) + let columns: Array<{ columnName: string; columnLabel: string; dataType: string }> = []; + + // 첫 번째 유효한 객체 찾기 + const firstValidItem = dataArray.find(item => item && typeof item === "object" && !Array.isArray(item)); + + if (firstValidItem) { + columns = Object.keys(firstValidItem).map((key) => ({ + columnName: key, + columnLabel: key, + dataType: typeof firstValidItem[key], + })); + logger.info(`추출된 컬럼 수: ${columns.length}, 컬럼명: [${columns.map(c => c.columnName).join(", ")}]`); + } else { + logger.warn("유효한 데이터 항목을 찾을 수 없어 컬럼을 추출할 수 없습니다."); + } + + return { + success: true, + data: { + rows: dataArray, + columns, + total: dataArray.length, + connectionInfo: { + connectionId: connection.id, + connectionName: connection.connection_name, + baseUrl: connection.base_url, + endpoint: effectiveEndpoint, + }, + }, + message: `${dataArray.length}개의 데이터를 조회했습니다.`, + }; + } catch (error) { + logger.error("REST API 데이터 조회 오류:", error); + return { + success: false, + message: "REST API 데이터 조회에 실패했습니다.", + error: { + code: "FETCH_ERROR", + details: error instanceof Error ? error.message : "알 수 없는 오류", + }, + }; + } + } + /** * 연결 데이터 유효성 검증 */ diff --git a/backend-node/src/services/mailSendSimpleService.ts b/backend-node/src/services/mailSendSimpleService.ts index b4dce503..4e44006a 100644 --- a/backend-node/src/services/mailSendSimpleService.ts +++ b/backend-node/src/services/mailSendSimpleService.ts @@ -334,9 +334,12 @@ class MailSendSimpleService { if (variables) { buttonText = this.replaceVariables(buttonText, variables); } + // styles 객체 또는 직접 속성에서 색상 가져오기 + const buttonBgColor = component.styles?.backgroundColor || component.backgroundColor || '#007bff'; + const buttonTextColor = component.styles?.color || component.textColor || '#fff'; // 버튼은 왼쪽 정렬 (text-align 제거) html += ``; break; case 'image': @@ -348,6 +351,89 @@ class MailSendSimpleService { case 'spacer': html += `
`; break; + case 'header': + html += ` +
+ + + + + +
+ ${component.logoSrc ? `로고` : ''} + ${component.brandName || ''} + + ${component.sendDate || ''} +
+
+ `; + break; + case 'infoTable': + html += ` +
+ ${component.tableTitle ? `
${component.tableTitle}
` : ''} + + ${(component.rows || []).map((row: any, i: number) => ` + + + + + `).join('')} +
${row.label}${row.value}
+
+ `; + break; + case 'alertBox': + const alertColors: Record = { + info: { bg: '#eff6ff', border: '#3b82f6', text: '#1e40af' }, + warning: { bg: '#fffbeb', border: '#f59e0b', text: '#92400e' }, + danger: { bg: '#fef2f2', border: '#ef4444', text: '#991b1b' }, + success: { bg: '#ecfdf5', border: '#10b981', text: '#065f46' } + }; + const colors = alertColors[component.alertType || 'info']; + html += ` +
+ ${component.alertTitle ? `
${component.alertTitle}
` : ''} +
${component.content || ''}
+
+ `; + break; + case 'divider': + html += `
`; + break; + case 'footer': + html += ` +
+ ${component.companyName ? `
${component.companyName}
` : ''} + ${(component.ceoName || component.businessNumber) ? ` +
+ ${component.ceoName ? `대표: ${component.ceoName}` : ''} + ${component.ceoName && component.businessNumber ? ' | ' : ''} + ${component.businessNumber ? `사업자등록번호: ${component.businessNumber}` : ''} +
+ ` : ''} + ${component.address ? `
${component.address}
` : ''} + ${(component.phone || component.email) ? ` +
+ ${component.phone ? `Tel: ${component.phone}` : ''} + ${component.phone && component.email ? ' | ' : ''} + ${component.email ? `Email: ${component.email}` : ''} +
+ ` : ''} + ${component.copyright ? `
${component.copyright}
` : ''} +
+ `; + break; + case 'numberedList': + html += ` +
+ ${component.listTitle ? `
${component.listTitle}
` : ''} +
    + ${(component.listItems || []).map((item: string) => `
  1. ${item}
  2. `).join('')} +
+
+ `; + break; } }); diff --git a/backend-node/src/services/mailTemplateFileService.ts b/backend-node/src/services/mailTemplateFileService.ts index adb72fff..bd82a7d2 100644 --- a/backend-node/src/services/mailTemplateFileService.ts +++ b/backend-node/src/services/mailTemplateFileService.ts @@ -4,13 +4,35 @@ import path from "path"; // MailComponent 인터페이스 정의 export interface MailComponent { id: string; - type: "text" | "button" | "image" | "spacer"; + type: "text" | "button" | "image" | "spacer" | "header" | "infoTable" | "alertBox" | "divider" | "footer" | "numberedList"; content?: string; text?: string; url?: string; src?: string; height?: number; styles?: Record; + // 헤더 컴포넌트용 + logoSrc?: string; + brandName?: string; + sendDate?: string; + headerBgColor?: string; + // 정보 테이블용 + rows?: Array<{ label: string; value: string }>; + tableTitle?: string; + // 강조 박스용 + alertType?: "info" | "warning" | "danger" | "success"; + alertTitle?: string; + // 푸터용 + companyName?: string; + ceoName?: string; + businessNumber?: string; + address?: string; + phone?: string; + email?: string; + copyright?: string; + // 번호 리스트용 + listItems?: string[]; + listTitle?: string; } // QueryConfig 인터페이스 정의 (사용하지 않지만 타입 호환성 유지) @@ -236,6 +258,89 @@ class MailTemplateFileService { case "spacer": html += `
`; break; + case "header": + html += ` +
+ + + + + +
+ ${comp.logoSrc ? `로고` : ''} + ${comp.brandName || ''} + + ${comp.sendDate || ''} +
+
+ `; + break; + case "infoTable": + html += ` +
+ ${comp.tableTitle ? `
${comp.tableTitle}
` : ''} + + ${(comp.rows || []).map((row, i) => ` + + + + + `).join('')} +
${row.label}${row.value}
+
+ `; + break; + case "alertBox": + const alertColors: Record = { + info: { bg: '#eff6ff', border: '#3b82f6', text: '#1e40af' }, + warning: { bg: '#fffbeb', border: '#f59e0b', text: '#92400e' }, + danger: { bg: '#fef2f2', border: '#ef4444', text: '#991b1b' }, + success: { bg: '#ecfdf5', border: '#10b981', text: '#065f46' } + }; + const colors = alertColors[comp.alertType || 'info']; + html += ` +
+ ${comp.alertTitle ? `
${comp.alertTitle}
` : ''} +
${comp.content || ''}
+
+ `; + break; + case "divider": + html += `
`; + break; + case "footer": + html += ` +
+ ${comp.companyName ? `
${comp.companyName}
` : ''} + ${(comp.ceoName || comp.businessNumber) ? ` +
+ ${comp.ceoName ? `대표: ${comp.ceoName}` : ''} + ${comp.ceoName && comp.businessNumber ? ' | ' : ''} + ${comp.businessNumber ? `사업자등록번호: ${comp.businessNumber}` : ''} +
+ ` : ''} + ${comp.address ? `
${comp.address}
` : ''} + ${(comp.phone || comp.email) ? ` +
+ ${comp.phone ? `Tel: ${comp.phone}` : ''} + ${comp.phone && comp.email ? ' | ' : ''} + ${comp.email ? `Email: ${comp.email}` : ''} +
+ ` : ''} + ${comp.copyright ? `
${comp.copyright}
` : ''} +
+ `; + break; + case "numberedList": + html += ` +
+ ${comp.listTitle ? `
${comp.listTitle}
` : ''} +
    + ${(comp.listItems || []).map(item => `
  1. ${item}
  2. `).join('')} +
+
+ `; + break; } }); diff --git a/backend-node/src/services/screenManagementService.ts b/backend-node/src/services/screenManagementService.ts index a7445637..71550fd6 100644 --- a/backend-node/src/services/screenManagementService.ts +++ b/backend-node/src/services/screenManagementService.ts @@ -70,12 +70,13 @@ export class ScreenManagementService { throw new Error("이미 존재하는 화면 코드입니다."); } - // 화면 생성 (Raw Query) + // 화면 생성 (Raw Query) - REST API 지원 추가 const [screen] = await query( `INSERT INTO screen_definitions ( screen_name, screen_code, table_name, company_code, description, created_by, - db_source_type, db_connection_id - ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + db_source_type, db_connection_id, data_source_type, rest_api_connection_id, + rest_api_endpoint, rest_api_json_path + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING *`, [ screenData.screenName, @@ -86,6 +87,10 @@ export class ScreenManagementService { screenData.createdBy, screenData.dbSourceType || "internal", screenData.dbConnectionId || null, + (screenData as any).dataSourceType || "database", + (screenData as any).restApiConnectionId || null, + (screenData as any).restApiEndpoint || null, + (screenData as any).restApiJsonPath || "data", ] ); @@ -1977,6 +1982,11 @@ export class ScreenManagementService { updatedBy: data.updated_by, dbSourceType: data.db_source_type || "internal", dbConnectionId: data.db_connection_id || undefined, + // REST API 관련 필드 + dataSourceType: data.data_source_type || "database", + restApiConnectionId: data.rest_api_connection_id || undefined, + restApiEndpoint: data.rest_api_endpoint || undefined, + restApiJsonPath: data.rest_api_json_path || "data", }; } diff --git a/backend-node/src/types/screen.ts b/backend-node/src/types/screen.ts index ca5a466f..8260f3c6 100644 --- a/backend-node/src/types/screen.ts +++ b/backend-node/src/types/screen.ts @@ -154,6 +154,11 @@ export interface ScreenDefinition { updatedBy?: string; dbSourceType?: "internal" | "external"; dbConnectionId?: number; + // REST API 관련 필드 + dataSourceType?: "database" | "restapi"; + restApiConnectionId?: number; + restApiEndpoint?: string; + restApiJsonPath?: string; } // 화면 생성 요청 @@ -166,6 +171,11 @@ export interface CreateScreenRequest { createdBy?: string; dbSourceType?: "internal" | "external"; dbConnectionId?: number; + // REST API 관련 필드 + dataSourceType?: "database" | "restapi"; + restApiConnectionId?: number; + restApiEndpoint?: string; + restApiJsonPath?: string; } // 화면 수정 요청 diff --git a/frontend/app/(main)/admin/mail/send/page.tsx b/frontend/app/(main)/admin/mail/send/page.tsx index 9f368f13..56922043 100644 --- a/frontend/app/(main)/admin/mail/send/page.tsx +++ b/frontend/app/(main)/admin/mail/send/page.tsx @@ -45,6 +45,7 @@ import { saveDraft, updateDraft, } from "@/lib/api/mail"; +import { API_BASE_URL } from "@/lib/api/client"; import { useToast } from "@/hooks/use-toast"; export default function MailSendPage() { @@ -498,7 +499,7 @@ ${data.originalBody}`; throw new Error("인증 토큰이 없습니다. 다시 로그인해주세요."); } - const response = await fetch("/api/mail/send/simple", { + const response = await fetch(`${API_BASE_URL}/mail/send/simple`, { method: "POST", headers: { Authorization: `Bearer ${authToken}`, @@ -1226,6 +1227,91 @@ ${data.originalBody}`; 여백 ); + + case 'header': + return ( +
+
+
+ {component.logoSrc && 로고} + {component.brandName} +
+ {component.sendDate} +
+
+ ); + + case 'infoTable': + return ( +
+ {component.tableTitle && ( +
{component.tableTitle}
+ )} + + + {component.rows?.map((row: any, i: number) => ( + + + + + ))} + +
{row.label}{row.value}
+
+ ); + + case 'alertBox': + return ( +
+ {component.alertTitle &&
{component.alertTitle}
} +
{component.content}
+
+ ); + + case 'divider': + return ( +
+ ); + + case 'footer': + return ( +
+ {component.companyName &&
{component.companyName}
} + {(component.ceoName || component.businessNumber) && ( +
+ {component.ceoName && 대표: {component.ceoName}} + {component.ceoName && component.businessNumber && |} + {component.businessNumber && 사업자등록번호: {component.businessNumber}} +
+ )} + {component.address &&
{component.address}
} + {(component.phone || component.email) && ( +
+ {component.phone && Tel: {component.phone}} + {component.phone && component.email && |} + {component.email && Email: {component.email}} +
+ )} + {component.copyright &&
{component.copyright}
} +
+ ); + + case 'numberedList': + return ( +
+ {component.listTitle &&
{component.listTitle}
} +
    + {component.listItems?.map((item: string, i: number) => ( +
  1. {item}
  2. + ))} +
+
+ ); default: return null; diff --git a/frontend/app/(main)/screens/[screenId]/page.tsx b/frontend/app/(main)/screens/[screenId]/page.tsx index 5685d23a..74f79f8f 100644 --- a/frontend/app/(main)/screens/[screenId]/page.tsx +++ b/frontend/app/(main)/screens/[screenId]/page.tsx @@ -239,17 +239,17 @@ function ScreenViewPage() { // 가로 기준 스케일 계산 (좌우 여백 16px씩 고정) const newScale = availableWidth / designWidth; - console.log("📐 스케일 계산:", { - containerWidth, - containerHeight, - MARGIN_X, - availableWidth, - designWidth, - designHeight, - finalScale: newScale, - "스케일된 화면 크기": `${designWidth * newScale}px × ${designHeight * newScale}px`, - "실제 좌우 여백": `${(containerWidth - designWidth * newScale) / 2}px씩`, - }); + // console.log("📐 스케일 계산:", { + // containerWidth, + // containerHeight, + // MARGIN_X, + // availableWidth, + // designWidth, + // designHeight, + // finalScale: newScale, + // "스케일된 화면 크기": `${designWidth * newScale}px × ${designHeight * newScale}px`, + // "실제 좌우 여백": `${(containerWidth - designWidth * newScale) / 2}px씩`, + // }); setScale(newScale); // 컨테이너 너비 업데이트 diff --git a/frontend/components/admin/RestApiConnectionModal.tsx b/frontend/components/admin/RestApiConnectionModal.tsx index 8795fa40..aa7d79d8 100644 --- a/frontend/components/admin/RestApiConnectionModal.tsx +++ b/frontend/components/admin/RestApiConnectionModal.tsx @@ -226,7 +226,7 @@ export function RestApiConnectionModal({ isOpen, onClose, onSave, connection }: endpoint_path: endpointPath || undefined, default_headers: defaultHeaders, default_method: defaultMethod, - default_body: defaultBody || undefined, + default_body: defaultBody.trim() || null, // 빈 문자열이면 null로 전송하여 DB 업데이트 auth_type: authType, auth_config: authType === "none" ? undefined : authConfig, timeout, @@ -236,6 +236,13 @@ export function RestApiConnectionModal({ isOpen, onClose, onSave, connection }: is_active: isActive ? "Y" : "N", }; + console.log("저장하려는 데이터:", { + connection_name: connectionName, + default_method: defaultMethod, + endpoint_path: endpointPath, + base_url: baseUrl, + }); + if (connection?.id) { await ExternalRestApiConnectionAPI.updateConnection(connection.id, data); toast({ @@ -303,7 +310,13 @@ export function RestApiConnectionModal({ isOpen, onClose, onSave, connection }: 기본 URL *
- { + setDefaultMethod(val); + setTestMethod(val); // 테스트 Method도 동기화 + }} + > diff --git a/frontend/components/common/ScreenModal.tsx b/frontend/components/common/ScreenModal.tsx index 65dbf84c..4eb45fd7 100644 --- a/frontend/components/common/ScreenModal.tsx +++ b/frontend/components/common/ScreenModal.tsx @@ -68,7 +68,7 @@ export const ScreenModal: React.FC = ({ className }) => { const savedMode = localStorage.getItem("screenModal_continuousMode"); if (savedMode === "true") { setContinuousMode(true); - console.log("🔄 연속 모드 복원: true"); + // console.log("🔄 연속 모드 복원: true"); } }, []); @@ -120,10 +120,28 @@ export const ScreenModal: React.FC = ({ className }) => { }; }; + // 🆕 선택된 데이터 상태 추가 (RepeatScreenModal 등에서 사용) + const [selectedData, setSelectedData] = useState[]>([]); + // 전역 모달 이벤트 리스너 useEffect(() => { const handleOpenModal = (event: CustomEvent) => { - const { screenId, title, description, size, urlParams } = event.detail; + const { screenId, title, description, size, urlParams, selectedData: eventSelectedData, selectedIds } = event.detail; + + console.log("📦 [ScreenModal] 모달 열기 이벤트 수신:", { + screenId, + title, + selectedData: eventSelectedData, + selectedIds, + }); + + // 🆕 선택된 데이터 저장 + if (eventSelectedData && Array.isArray(eventSelectedData)) { + setSelectedData(eventSelectedData); + console.log("📦 [ScreenModal] 선택된 데이터 저장:", eventSelectedData.length, "건"); + } else { + setSelectedData([]); + } // 🆕 URL 파라미터가 있으면 현재 URL에 추가 if (urlParams && typeof window !== "undefined") { @@ -164,6 +182,7 @@ export const ScreenModal: React.FC = ({ className }) => { }); setScreenData(null); setFormData({}); + setSelectedData([]); // 🆕 선택된 데이터 초기화 setContinuousMode(false); localStorage.setItem("screenModal_continuousMode", "false"); // localStorage에 저장 console.log("🔄 연속 모드 초기화: false"); @@ -605,6 +624,8 @@ export const ScreenModal: React.FC = ({ className }) => { userId={userId} userName={userName} companyCode={user?.companyCode} + // 🆕 선택된 데이터 전달 (RepeatScreenModal 등에서 사용) + groupedData={selectedData.length > 0 ? selectedData : undefined} /> ); })} diff --git a/frontend/components/mail/MailDesigner.tsx b/frontend/components/mail/MailDesigner.tsx index 8d3a38f9..464de85d 100644 --- a/frontend/components/mail/MailDesigner.tsx +++ b/frontend/components/mail/MailDesigner.tsx @@ -19,19 +19,50 @@ import { Trash2, Settings, Upload, - X + X, + GripVertical, + ChevronUp, + ChevronDown, + LayoutTemplate, + Table2, + AlertCircle, + Minus, + Building2, + ListOrdered } from "lucide-react"; import { getMailTemplates } from "@/lib/api/mail"; export interface MailComponent { id: string; - type: "text" | "button" | "image" | "spacer" | "table"; + type: "text" | "button" | "image" | "spacer" | "table" | "header" | "infoTable" | "alertBox" | "divider" | "footer" | "numberedList"; content?: string; text?: string; url?: string; src?: string; height?: number; styles?: Record; + // 헤더 컴포넌트용 + logoSrc?: string; + brandName?: string; + sendDate?: string; + headerBgColor?: string; + // 정보 테이블용 + rows?: Array<{ label: string; value: string }>; + tableTitle?: string; + // 강조 박스용 + alertType?: "info" | "warning" | "danger" | "success"; + alertTitle?: string; + // 푸터용 + companyName?: string; + ceoName?: string; + businessNumber?: string; + address?: string; + phone?: string; + email?: string; + copyright?: string; + // 번호 리스트용 + listItems?: string[]; + listTitle?: string; } export interface QueryConfig { @@ -64,6 +95,10 @@ export default function MailDesigner({ const [subject, setSubject] = useState(""); const [queries, setQueries] = useState([]); const [isLoading, setIsLoading] = useState(false); + + // 드래그 앤 드롭 상태 + const [draggedIndex, setDraggedIndex] = useState(null); + const [dragOverIndex, setDragOverIndex] = useState(null); // 템플릿 데이터 로드 (수정 모드) useEffect(() => { @@ -96,10 +131,18 @@ export default function MailDesigner({ // 컴포넌트 타입 정의 const componentTypes = [ - { type: "text", icon: Type, label: "텍스트", color: "bg-primary/20 hover:bg-blue-200" }, - { type: "button", icon: MousePointer, label: "버튼", color: "bg-success/20 hover:bg-success/30" }, - { type: "image", icon: ImageIcon, label: "이미지", color: "bg-purple-100 hover:bg-purple-200" }, - { type: "spacer", icon: Square, label: "여백", color: "bg-muted hover:bg-muted/80" }, + // 레이아웃 컴포넌트 + { type: "header", icon: LayoutTemplate, label: "헤더", color: "bg-indigo-100 hover:bg-indigo-200", category: "layout" }, + { type: "divider", icon: Minus, label: "구분선", color: "bg-gray-100 hover:bg-gray-200", category: "layout" }, + { type: "spacer", icon: Square, label: "여백", color: "bg-muted hover:bg-muted/80", category: "layout" }, + { type: "footer", icon: Building2, label: "푸터", color: "bg-slate-100 hover:bg-slate-200", category: "layout" }, + // 컨텐츠 컴포넌트 + { type: "text", icon: Type, label: "텍스트", color: "bg-primary/20 hover:bg-blue-200", category: "content" }, + { type: "button", icon: MousePointer, label: "버튼", color: "bg-success/20 hover:bg-success/30", category: "content" }, + { type: "image", icon: ImageIcon, label: "이미지", color: "bg-purple-100 hover:bg-purple-200", category: "content" }, + { type: "infoTable", icon: Table2, label: "정보 테이블", color: "bg-cyan-100 hover:bg-cyan-200", category: "content" }, + { type: "alertBox", icon: AlertCircle, label: "안내 박스", color: "bg-amber-100 hover:bg-amber-200", category: "content" }, + { type: "numberedList", icon: ListOrdered, label: "번호 리스트", color: "bg-emerald-100 hover:bg-emerald-200", category: "content" }, ]; // 컴포넌트 추가 @@ -107,21 +150,75 @@ export default function MailDesigner({ const newComponent: MailComponent = { id: `comp-${Date.now()}`, type: type as any, - content: type === "text" ? "" : undefined, // 🎯 빈 문자열로 시작 (HTML 태그 제거) - text: type === "button" ? "버튼 텍스트" : undefined, // 🎯 더 명확한 기본값 - url: type === "button" || type === "image" ? "" : undefined, // 🎯 빈 문자열로 시작 - src: type === "image" ? "https://placehold.co/600x200/e5e7eb/64748b?text=이미지를+업로드하세요" : undefined, // 🎯 한글 안내 - height: type === "spacer" ? 30 : undefined, // 🎯 기본값 30px로 증가 (더 적절한 간격) + content: type === "text" ? "" : undefined, + text: type === "button" ? "버튼 텍스트" : undefined, + url: type === "button" || type === "image" ? "" : undefined, + src: type === "image" ? "https://placehold.co/600x200/e5e7eb/64748b?text=이미지를+업로드하세요" : undefined, + height: type === "spacer" ? 30 : type === "divider" ? 1 : undefined, styles: { - padding: "10px", + padding: type === "divider" ? "0" : "10px", backgroundColor: type === "button" ? "#007bff" : "transparent", color: type === "button" ? "#fff" : "#333", }, + // 헤더 기본값 + logoSrc: type === "header" ? "" : undefined, + brandName: type === "header" ? "회사명" : undefined, + sendDate: type === "header" ? new Date().toLocaleDateString("ko-KR") : undefined, + headerBgColor: type === "header" ? "#f8f9fa" : undefined, + // 정보 테이블 기본값 + rows: type === "infoTable" ? [{ label: "항목", value: "내용" }] : undefined, + tableTitle: type === "infoTable" ? "" : undefined, + // 안내 박스 기본값 + alertType: type === "alertBox" ? "info" : undefined, + alertTitle: type === "alertBox" ? "안내" : undefined, + // 푸터 기본값 + companyName: type === "footer" ? "회사명" : undefined, + ceoName: type === "footer" ? "" : undefined, + businessNumber: type === "footer" ? "" : undefined, + address: type === "footer" ? "" : undefined, + phone: type === "footer" ? "" : undefined, + email: type === "footer" ? "" : undefined, + copyright: type === "footer" ? `© ${new Date().getFullYear()} All rights reserved.` : undefined, + // 번호 리스트 기본값 + listItems: type === "numberedList" ? ["첫 번째 항목"] : undefined, + listTitle: type === "numberedList" ? "" : undefined, }; setComponents([...components, newComponent]); }; + // 드래그 앤 드롭 핸들러 + const handleDragStart = (index: number) => { + setDraggedIndex(index); + }; + + const handleDragOver = (e: React.DragEvent, index: number) => { + e.preventDefault(); + if (draggedIndex !== null && draggedIndex !== index) { + setDragOverIndex(index); + } + }; + + const handleDrop = (index: number) => { + if (draggedIndex !== null && draggedIndex !== index) { + moveComponent(draggedIndex, index); + } + setDraggedIndex(null); + setDragOverIndex(null); + }; + + const handleDragEnd = () => { + setDraggedIndex(null); + setDragOverIndex(null); + }; + + const moveComponent = (fromIndex: number, toIndex: number) => { + const newComponents = [...components]; + const [movedItem] = newComponents.splice(fromIndex, 1); + newComponents.splice(toIndex, 0, movedItem); + setComponents(newComponents); + }; + // 컴포넌트 삭제 const removeComponent = (id: string) => { setComponents(components.filter(c => c.id !== id)); @@ -189,13 +286,35 @@ export default function MailDesigner({
{/* 왼쪽: 컴포넌트 팔레트 */}
+ {/* 레이아웃 컴포넌트 */} +
+

+ + 레이아웃 +

+
+ {componentTypes.filter(c => c.category === "layout").map(({ type, icon: Icon, label, color }) => ( + + ))} +
+
+ + {/* 컨텐츠 컴포넌트 */}

- 컴포넌트 + 컨텐츠

- {componentTypes.map(({ type, icon: Icon, label, color }) => ( + {componentTypes.filter(c => c.category === "content").map(({ type, icon: Icon, label, color }) => ( +
+ +
+ +
+ + {/* 순서 배지 */} +
+ {index + 1} +
+ {/* 삭제 버튼 */}
-
-

- 추천값:
- • 좁은 간격: 10~20 픽셀
- • 보통 간격: 30~50 픽셀
- • 넓은 간격: 60~100 픽셀 -

+
+
+ )} + + {/* 헤더 컴포넌트 */} + {selected.type === "header" && ( +
+
+ + updateComponent(selected.id, { brandName: e.target.value })} + placeholder="회사명" + className="mt-1" + /> +
+
+ + updateComponent(selected.id, { logoSrc: e.target.value })} + placeholder="https://example.com/logo.png" + className="mt-1" + /> +
+
+ + updateComponent(selected.id, { sendDate: e.target.value })} + className="mt-1" + /> +
+
+ +
+ updateComponent(selected.id, { headerBgColor: e.target.value })} + className="w-16 h-10" + /> + {selected.headerBgColor || "#f8f9fa"} +
+
+
+ )} + + {/* 정보 테이블 컴포넌트 */} + {selected.type === "infoTable" && ( +
+
+ + updateComponent(selected.id, { tableTitle: e.target.value })} + placeholder="예: 주문 정보" + className="mt-1" + /> +
+
+ +
+ {selected.rows?.map((row, i) => ( +
+ { + const newRows = [...(selected.rows || [])]; + newRows[i] = { ...newRows[i], label: e.target.value }; + updateComponent(selected.id, { rows: newRows }); + }} + placeholder="항목명" + className="flex-1" + /> + { + const newRows = [...(selected.rows || [])]; + newRows[i] = { ...newRows[i], value: e.target.value }; + updateComponent(selected.id, { rows: newRows }); + }} + placeholder="값" + className="flex-1" + /> + +
+ ))} + +
+
+
+ )} + + {/* 안내 박스 컴포넌트 */} + {selected.type === "alertBox" && ( +
+
+ +
+ {(["info", "warning", "danger", "success"] as const).map((type) => ( + + ))} +
+
+
+ + updateComponent(selected.id, { alertTitle: e.target.value })} + placeholder="안내 제목" + className="mt-1" + /> +
+
+ +