-
Notifications
You must be signed in to change notification settings - Fork 1
[UX 개선] 메일 상세 뷰 우측 패널 레이아웃 적용 #1335
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -8,7 +8,7 @@ import { Checkbox } from "@/components/ui/checkbox"; | |||||||||||||||||||||
| import { Button } from "@/components/ui/button"; | ||||||||||||||||||||||
| import { Textarea } from "@/components/ui/textarea"; | ||||||||||||||||||||||
| import { Input } from "@/components/ui/input"; | ||||||||||||||||||||||
| import { Loader2, MessagesSquare } from "lucide-react"; | ||||||||||||||||||||||
| import { Loader2, MessagesSquare, Users, Paperclip, Calendar } from "lucide-react"; | ||||||||||||||||||||||
| import { DecisionPointCard } from "@/components/DecisionPointCard"; | ||||||||||||||||||||||
| import { SourceDrawer } from "@/components/SourceDrawer"; | ||||||||||||||||||||||
| import { | ||||||||||||||||||||||
|
|
@@ -29,6 +29,9 @@ import { | |||||||||||||||||||||
| type EmailData = ThreadEmailData & { | ||||||||||||||||||||||
| requires_reply?: boolean; | ||||||||||||||||||||||
| schedule_conflict?: boolean; | ||||||||||||||||||||||
| attachments?: Array<{ id: string; name: string; size: string }>; | ||||||||||||||||||||||
| participants?: Array<{ name: string; email: string; role: string }>; | ||||||||||||||||||||||
| meeting_proposals?: Array<{ id: string; title: string; time: string }>; | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
| interface LlmData { | ||||||||||||||||||||||
| summary: string; | ||||||||||||||||||||||
|
|
@@ -649,8 +652,9 @@ export const EmailDetail = memo(function EmailDetail({ emailId, actionCommand = | |||||||||||||||||||||
| </div> | ||||||||||||||||||||||
| </div> | ||||||||||||||||||||||
| <Separator /> | ||||||||||||||||||||||
| <ScrollArea className="flex-1"> | ||||||||||||||||||||||
| <div className="flex flex-col gap-6 bg-background/50 p-6 pb-[calc(7rem+env(safe-area-inset-bottom))] lg:pb-6"> | ||||||||||||||||||||||
| <div className="flex flex-1 min-h-0 overflow-hidden"> | ||||||||||||||||||||||
| <ScrollArea className="flex-1"> | ||||||||||||||||||||||
| <div className="flex flex-col gap-6 bg-background/50 p-6 pb-[calc(7rem+env(safe-area-inset-bottom))] lg:pb-6"> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| <DecisionPointCard | ||||||||||||||||||||||
| title="맥락 종합" | ||||||||||||||||||||||
|
|
@@ -864,6 +868,65 @@ export const EmailDetail = memo(function EmailDetail({ emailId, actionCommand = | |||||||||||||||||||||
| </DecisionPointCard> | ||||||||||||||||||||||
| </div> | ||||||||||||||||||||||
| </ScrollArea> | ||||||||||||||||||||||
| <aside className="w-80 flex-shrink-0 bg-background/50 p-6 overflow-y-auto hidden xl:block border-l border-border space-y-6"> | ||||||||||||||||||||||
| <div className="space-y-4"> | ||||||||||||||||||||||
| <h3 className="text-sm font-bold flex items-center gap-2"> | ||||||||||||||||||||||
| <Users className="w-4 h-4 text-primary" /> | ||||||||||||||||||||||
| 참여자 | ||||||||||||||||||||||
| </h3> | ||||||||||||||||||||||
| {email.participants && email.participants.length > 0 ? ( | ||||||||||||||||||||||
| <ul className="space-y-2"> | ||||||||||||||||||||||
| {email.participants.map((p, i) => ( | ||||||||||||||||||||||
| <li key={i} className="text-sm flex flex-col"> | ||||||||||||||||||||||
| <span className="font-medium">{p.name}</span> | ||||||||||||||||||||||
| <span className="text-xs text-muted-foreground">{p.email} ({p.role})</span> | ||||||||||||||||||||||
| </li> | ||||||||||||||||||||||
|
Comment on lines
+879
to
+883
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win Do not render raw participant email addresses. Line 882 places Render the participant name and role only. Do not render Proposed fix- <span className="text-xs text-muted-foreground">{p.email} ({p.role})</span>
+ <span className="text-xs text-muted-foreground">{p.role}</span>As per coding guidelines, avoid displaying “raw message data”. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI AgentsSource: Coding guidelines |
||||||||||||||||||||||
| ))} | ||||||||||||||||||||||
| </ul> | ||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||
| <p className="text-xs text-muted-foreground">참여자 정보가 없습니다.</p> | ||||||||||||||||||||||
| )} | ||||||||||||||||||||||
| </div> | ||||||||||||||||||||||
| <Separator /> | ||||||||||||||||||||||
| <div className="space-y-4"> | ||||||||||||||||||||||
| <h3 className="text-sm font-bold flex items-center gap-2"> | ||||||||||||||||||||||
| <Paperclip className="w-4 h-4 text-primary" /> | ||||||||||||||||||||||
| 첨부 파일 | ||||||||||||||||||||||
| </h3> | ||||||||||||||||||||||
| {email.attachments && email.attachments.length > 0 ? ( | ||||||||||||||||||||||
| <ul className="space-y-2"> | ||||||||||||||||||||||
| {email.attachments.map((a, i) => ( | ||||||||||||||||||||||
| <li key={i} className="text-sm flex flex-col"> | ||||||||||||||||||||||
| <span className="font-medium">{a.name}</span> | ||||||||||||||||||||||
| <span className="text-xs text-muted-foreground">{a.size}</span> | ||||||||||||||||||||||
| </li> | ||||||||||||||||||||||
| ))} | ||||||||||||||||||||||
| </ul> | ||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||
| <p className="text-xs text-muted-foreground">첨부 파일이 없습니다.</p> | ||||||||||||||||||||||
| )} | ||||||||||||||||||||||
| </div> | ||||||||||||||||||||||
| <Separator /> | ||||||||||||||||||||||
| <div className="space-y-4"> | ||||||||||||||||||||||
| <h3 className="text-sm font-bold flex items-center gap-2"> | ||||||||||||||||||||||
| <Calendar className="w-4 h-4 text-primary" /> | ||||||||||||||||||||||
| 일정 제안 | ||||||||||||||||||||||
| </h3> | ||||||||||||||||||||||
| {email.meeting_proposals && email.meeting_proposals.length > 0 ? ( | ||||||||||||||||||||||
| <ul className="space-y-2"> | ||||||||||||||||||||||
| {email.meeting_proposals.map((m, i) => ( | ||||||||||||||||||||||
| <li key={i} className="text-sm flex flex-col"> | ||||||||||||||||||||||
| <span className="font-medium">{m.title}</span> | ||||||||||||||||||||||
| <span className="text-xs text-muted-foreground">{m.time}</span> | ||||||||||||||||||||||
| </li> | ||||||||||||||||||||||
| ))} | ||||||||||||||||||||||
| </ul> | ||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||
| <p className="text-xs text-muted-foreground">제안된 일정이 없습니다.</p> | ||||||||||||||||||||||
| )} | ||||||||||||||||||||||
| </div> | ||||||||||||||||||||||
| </aside> | ||||||||||||||||||||||
| </div> | ||||||||||||||||||||||
| <SourceDrawer | ||||||||||||||||||||||
| open={sourceDrawerOpen} | ||||||||||||||||||||||
| title="맥락 종합 근거" | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import { chromium } from '@playwright/test'; | ||
|
|
||
| (async () => { | ||
| const browser = await chromium.launch({ headless: true }); | ||
| const page = await browser.newPage(); | ||
| try { | ||
| // Navigate straight to a known layout component containing EmailDetail if possible, or force the inbox view | ||
| await page.goto('http://localhost:3000', { waitUntil: 'networkidle' }); | ||
|
|
||
| // Click the "메일" button in the GNB to go to the inbox | ||
| const mailTab = page.locator('button').filter({ hasText: '메일' }).first(); | ||
| if (await mailTab.isVisible()) { | ||
| await mailTab.click(); | ||
| await page.waitForTimeout(1000); | ||
| } | ||
|
|
||
| // Attempt to click the first email in the list | ||
| const firstEmail = page.locator('.group').first(); | ||
| if (await firstEmail.isVisible()) { | ||
| await firstEmail.click(); | ||
| } | ||
|
|
||
| // Wait for the EmailDetail side panel elements | ||
| await page.waitForTimeout(1500); // Give it time to render the layout | ||
| await page.screenshot({ path: '/home/jules/verification/email_detail_panel.png' }); | ||
| console.log("Screenshot saved"); | ||
| } catch (err) { | ||
| console.error("Error:", err); | ||
| } finally { | ||
|
Comment on lines
+18
to
+29
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift Make the browser verification deterministic and failing. The The catch block only logs errors. CI can pass after navigation, selector, or screenshot failures. The Use stable semantic locators for a known email fixture. Wait for and assert the complementary sidebar and its section headings. Save artifacts under a repository-relative directory. Set a non-zero exit code after any failure. 🤖 Prompt for AI Agents |
||
| await browser.close(); | ||
| } | ||
| })(); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add sidebar rendering assertions.
These lines only extend the icon mock. Add Vitest coverage for populated and empty Participants, Attachments, and Meeting Proposals sections.
Assert the visible section headings and rendered metadata. Do not add assertions for raw participant email addresses.
As per coding guidelines, update affected tests, mocks, and documentation in the same PR.
🤖 Prompt for AI Agents
Source: Coding guidelines