From 327602ad9c89f3e23be0fcd9e1d6a59eae70df39 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Wed, 5 Aug 2026 02:35:17 +0000 Subject: [PATCH 1/6] =?UTF-8?q?[UX=20=EA=B0=9C=EC=84=A0]=20EmailDetail?= =?UTF-8?q?=EC=97=90=20=EC=B0=B8=EC=97=AC=EC=9E=90=20=EB=AA=A9=EB=A1=9D,?= =?UTF-8?q?=20=EC=B2=A8=EB=B6=80=ED=8C=8C=EC=9D=BC=20=EB=A0=88=EC=9D=BC,?= =?UTF-8?q?=20=ED=9A=8C=EC=9D=98=20=EC=A0=9C=EC=95=88=20=ED=8C=A8=EB=84=90?= =?UTF-8?q?=20=EB=B0=8F=20=EB=AA=A8=EB=B0=94=EC=9D=BC=20=EB=8C=80=EC=9D=91?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/EmailDetail.test.tsx | 38 ++++++++++++++++++++ frontend/src/components/EmailDetail.tsx | 27 +++++++++++++- screenshot.mjs | 12 ------- 3 files changed, 64 insertions(+), 13 deletions(-) delete mode 100644 screenshot.mjs diff --git a/frontend/src/components/EmailDetail.test.tsx b/frontend/src/components/EmailDetail.test.tsx index a36eeaad5..10fdbf21d 100644 --- a/frontend/src/components/EmailDetail.test.tsx +++ b/frontend/src/components/EmailDetail.test.tsx @@ -1310,4 +1310,42 @@ describe("EmailDetail", () => { expect(container.textContent).toContain("답장 전송에 실패했습니다."); }); + + it("renders desktop high-density variants (participant list, attachment rail) and meeting proposal panel", async () => { + const email = { + id: 30, + message_id: "", + thread_id: null, + sender: "sender@example.com", + recipients: "user@example.com", + subject: "UI Density", + date: "2026-05-18T10:00:00Z", + body: "High density UI", + schedule_conflict: true, + requires_reply: true, + attachments: ["proposal.pdf", "schedule.xlsx"], + }; + const fetchMock = vi.fn((input) => { + const url = String(input); + if (url.endsWith("/api/emails/30")) return Promise.resolve(jsonResponse(email)); + if (url.endsWith("/api/llm/summarize")) { + return Promise.resolve(jsonResponse({ summary: "Summary", action_items: [] })); + } + throw new Error(`Unexpected fetch: ${url}`); + }); + vi.stubGlobal("fetch", fetchMock); + + container = document.createElement("div"); + document.body.appendChild(container); + root = createRoot(container); + await act(async () => { root?.render(); }); + await flushAsyncWork(); + + expect(container.textContent).toContain("sender@example.com, user@example.com"); + expect(container.textContent).toContain("참여자"); + expect(container.textContent).toContain("첨부파일"); + expect(container.textContent).toContain("proposal.pdf"); + expect(container.textContent).toContain("회의 제안 확인"); + expect(container.querySelector(".hidden.md\\:flex")).not.toBeNull(); + }); }); diff --git a/frontend/src/components/EmailDetail.tsx b/frontend/src/components/EmailDetail.tsx index 35263d783..1040747c7 100644 --- a/frontend/src/components/EmailDetail.tsx +++ b/frontend/src/components/EmailDetail.tsx @@ -29,6 +29,7 @@ import { type EmailData = ThreadEmailData & { requires_reply?: boolean; schedule_conflict?: boolean; + attachments?: string[]; }; interface LlmData { summary: string; @@ -567,6 +568,7 @@ export function EmailDetail({ emailId, actionCommand = null }: { emailId: number const safeEmailSender = toMailDisplayText(email.sender, '보낸 사람'); const safeEmailSubject = toMailDisplayText(email.subject, '(제목 없음)'); const safeReplyTo = toMailDisplayText(email.reply_to || email.sender, '답장 주소 없음'); + const safeRecipients = toMailDisplayText((email as ThreadEmailData & { recipients?: string }).recipients || email.sender, '참여자 없음'); const confidencePercent = toConfidencePercent(llmData?.confidence); const actionItems = llmData?.action_items ?? []; @@ -628,9 +630,21 @@ export function EmailDetail({ emailId, actionCommand = null }: { emailId: number
{safeEmailSender}
-
+
답장 주소: {safeReplyTo}
+
+ 참여자: + {safeEmailSender}, {safeRecipients} +
+ {email.attachments && email.attachments.length > 0 && ( +
+ 첨부파일: + {email.attachments.map((file, idx) => ( + {file} + ))} +
+ )}
@@ -649,6 +663,17 @@ export function EmailDetail({ emailId, actionCommand = null }: { emailId: number
+ {email.schedule_conflict && ( +
+
+
+

회의 제안 확인

+

이메일에 포함된 회의 일정을 캘린더와 조율합니다.

+
+ +
+
+ )}