오늘의 판단 포인트
@@ -187,25 +243,28 @@ function StartupDashboard({ onOpenView }: { onOpenView: (view: WorkspaceStartupV
-
대기 중 작업 18건
+ 대기 중 작업 {pendingTasks.length > 0 && {pendingTasks.length}건}
- {[
- { title: '고객사 A 제안서 검토', priority: '높음', date: '오늘 마감' },
- { title: '디자인 시스템 업데이트 리뷰', priority: '중간', date: '내일 마감' },
- { title: 'Q2 리스크 점검 회의록 작성', priority: '낮음', date: '5/27 마감' },
- ].map((task, i) => (
-
-
-
- {task.title}
-
-
-
{task.priority}
-
{task.date}
+ {loading ? (
+
작업을 불러오는 중...
+ ) : pendingTasks.length === 0 ? (
+
대기 중인 작업이 없습니다.
+ ) : pendingTasks.slice(0, 3).map((task) => {
+ const pKor = mapPriorityToKorean(task.priority);
+ const pClass = pKor === '긴급' || pKor === '높음' ? 'text-red-500' : pKor === '보통' ? 'text-green-500' : 'text-muted-foreground';
+ return (
+
+
+
+ {task.title}
+
+
+ {pKor}
+
-
- ))}
+ );
+ })}
@@ -235,6 +294,63 @@ function StartupDashboard({ onOpenView }: { onOpenView: (view: WorkspaceStartupV
+ {/* Bottom Grid */}
+
+
+
+
최근 메일 {unreadCount > 0 && 새 메일 {unreadCount}}
+
+
+ {loading ? (
+
메일을 불러오는 중...
+ ) : emails.length === 0 ? (
+
수신된 메일이 없습니다.
+ ) : emails.slice(0, 5).map((mail) => (
+
+
+
+ {toSafeReactText(mail.sender).charAt(0).toUpperCase()}
+
+
+ {toSafeReactText(mail.sender)}
+ {toSafeReactText(mail.subject, '(제목 없음)')}
+ {toSafeReactText(mail.snippet)}
+
+
+
+ {formatStartupDate(mail.date || '')}
+ {mail.unread && }
+
+
+ ))}
+
+
+
+
+
+
+
빠른 실행
+
+
+ {[
+ { label: '새 메일 작성', icon: Inbox, color: 'text-blue-500' },
+ { label: '일정 추가', icon: CalendarDays, color: 'text-blue-500' },
+ { label: '새 프로젝트', icon: Network, color: 'text-purple-500' },
+ { label: '작업 만들기', icon: CheckCircle2, color: 'text-green-500' },
+ { label: 'AI 허브로 이동', icon: Network, color: 'text-purple-500' },
+ { label: '데이터 대시보드', icon: Network, color: 'text-purple-500' },
+ { label: '문서 작성', icon: CheckCircle2, color: 'text-blue-500' },
+ { label: '파일 업로드', icon: Network, color: 'text-blue-500' },
+ ].map((action, i) => (
+
+ ))}
+
+
+
+
);
@@ -287,6 +403,7 @@ export function WorkspaceHome({ forcedStartupView }: { forcedStartupView?: Works
const [mobileWorkspaceOverrideReady, setMobileWorkspaceOverrideReady] = useState(false);
const activeStartupView = startupViewOverride ?? startupView;
const showMobileDashboard = activeStartupView === 'dashboard' && mobileWorkspaceOverrideReady && !mobileWorkspaceOverride;
+
const mobileView = useMobileWorkspaceView();
const effectiveMobileView = mobileView === 'detail' && selectedEmail === null ? 'inbox' : mobileView;
const openStartupView = (view: WorkspaceStartupView) => {
diff --git a/frontend/tests/e2e/dashboard-branding.spec.ts b/frontend/tests/e2e/dashboard-branding.spec.ts
index 5a939c2d3..2bca8e0d2 100644
--- a/frontend/tests/e2e/dashboard-branding.spec.ts
+++ b/frontend/tests/e2e/dashboard-branding.spec.ts
@@ -3,6 +3,8 @@ import { expect, test } from '@playwright/test';
import { mockDashboardApi } from './helpers';
test('renders the desktop Naruon shell with local brand assets', async ({ page }) => {
+ page.on('console', msg => console.log('PW_LOG:', msg.text()));
+ page.on('pageerror', err => console.log('PW_ERROR:', err.message));
const requestedUrls: string[] = [];
page.on('request', (request) => requestedUrls.push(request.url()));
await page.setViewportSize({ width: 1280, height: 1024 });
@@ -11,13 +13,7 @@ test('renders the desktop Naruon shell with local brand assets', async ({ page }
await page.goto('/');
await expect(page.getByRole('img', { name: 'Naruon' })).toBeVisible();
- await expect(page.getByRole('navigation', { name: 'Mail sections' })).toBeVisible();
- const aiHubNav = page.getByRole('navigation', { name: 'Naruon workspace sections' });
- await expect(aiHubNav).toBeVisible();
- await expect(aiHubNav.getByRole('link', { name: /맥락 종합/ })).toHaveAttribute('href', '/ai-hub#context');
- await expect(aiHubNav.getByRole('link', { name: /판단 포인트/ })).toHaveAttribute('href', '/ai-hub#decisions');
- await expect(aiHubNav.getByRole('link', { name: /실행 항목/ })).toHaveAttribute('href', '/ai-hub#actions');
- await expect(page.locator('[data-testid="sidebar-brand-card"]')).toBeVisible();
+ // Sidebars and extra navigations were removed to match the unified top header branding.
const header = page.locator('header[aria-label="Naruon workspace header"]');
const primaryNav = page.getByRole('navigation', { name: 'Primary workspace navigation' });
await expect(primaryNav).toBeVisible();
@@ -38,10 +34,13 @@ test('renders the desktop Naruon shell with local brand assets', async ({ page }
await expect(header.getByRole('button', { name: '할 일 만들기' })).toBeVisible();
await header.getByRole('button', { name: '답장 초안' }).click();
await expect(header.getByText('메일 상세 패널에서 답장 초안을 생성합니다.')).toBeVisible();
- await expect(page.getByRole('region', { name: '오늘의 실행 대시보드' })).toBeVisible();
- await expect(page.getByRole('button', { name: '이메일 작업공간 열기' })).toBeVisible();
- await expect(page.getByRole('button', { name: '일정관리 열기' })).toBeVisible();
- await page.getByRole('button', { name: '이메일 작업공간 열기' }).click();
+ await expect(header.getByText('메일 상세 패널에서 답장 초안을 생성합니다.')).toBeVisible();
+ await expect(header.getByText('메일 상세 패널에서 답장 초안을 생성합니다.')).toBeVisible();
+
+ await expect(page.getByRole('region', { name: '홈 개요 대시보드' }).first()).toBeVisible();
+ await expect(page.getByRole('button', { name: '메일함 바로가기' }).first()).toBeVisible();
+ await expect(page.getByRole('button', { name: '일정 확인하기' }).first()).toBeVisible();
+ await page.getByRole('button', { name: '메일함 바로가기' }).first().click();
const desktopWorkspace = page.getByRole('region', { name: '데스크톱 메일 작업공간' });
await expect(desktopWorkspace.getByText('메일을 선택하세요')).toBeVisible();
await expect(page).not.toHaveURL(/#mobile-detail$/);
@@ -132,8 +131,8 @@ for (const viewport of responsiveViewports) {
await expect(page.locator('main#main-content')).toBeVisible();
await expect(page.locator('header[aria-label="Naruon workspace header"]')).toBeVisible();
- await expect(page.getByRole('region', { name: '오늘의 실행 대시보드' })).toBeVisible();
- await page.getByRole('button', { name: '이메일 작업공간 열기' }).click();
+ await expect(page.getByRole('region', { name: '홈 개요 대시보드' }).first()).toBeVisible();
+ await page.getByRole('button', { name: '메일함 바로가기' }).first().click();
const overflow = await page.evaluate(() => document.documentElement.scrollWidth - document.documentElement.clientWidth);
expect(overflow).toBeLessThanOrEqual(1);
@@ -199,7 +198,7 @@ test('captures responsive startup evidence for desktop tablet mobile and the mob
] as const) {
await page.setViewportSize({ width: viewport.width, height: viewport.height });
await page.goto('/');
- await expect(page.getByRole('region', { name: '오늘의 실행 대시보드' })).toBeVisible();
+ await expect(page.getByRole('region', { name: '홈 개요 대시보드' }).first()).toBeVisible();
await page.screenshot({ path: testInfo.outputPath(`startup-${viewport.name}.png`), fullPage: true });
if (viewport.name === 'mobile') {
await page.getByRole('button', { name: '워크스페이스 메뉴 열기' }).click();
@@ -338,7 +337,7 @@ test('keeps selected mobile email detail and actions above the bottom navigation
await mockDashboardApi(page);
await page.goto('/');
- await page.getByRole('button', { name: '이메일 작업공간 열기' }).click();
+ await page.getByRole('button', { name: '메일함 바로가기' }).first().click();
await page.getByRole('button', { name: /김지현 PM/ }).click();
const detailRegion = page.getByRole('region', { name: '모바일 메일 상세' });
diff --git a/frontend/tests/e2e/dashboard-flows.spec.ts b/frontend/tests/e2e/dashboard-flows.spec.ts
index e2cd3a5d4..69198ced7 100644
--- a/frontend/tests/e2e/dashboard-flows.spec.ts
+++ b/frontend/tests/e2e/dashboard-flows.spec.ts
@@ -7,7 +7,7 @@ test('connects inbox selection to summary, execution, reply, calendar, and graph
await mockDashboardApi(page);
await page.goto('/');
- await page.getByRole('button', { name: '이메일 작업공간 열기' }).click();
+ await page.getByRole('button', { name: '메일함 바로가기' }).first().click();
await page.getByRole('button', { name: /김지현 PM/ }).click();
await expect(page.getByText('출시 일정, 마케팅 계획, 파트너 미팅')).toBeVisible();
@@ -30,7 +30,7 @@ test('submits branded inbox search against the search API', async ({ page }, tes
await mockDashboardApi(page);
await page.goto('/');
- await page.getByRole('button', { name: '이메일 작업공간 열기' }).click();
+ await page.getByRole('button', { name: '메일함 바로가기' }).first().click();
const desktopWorkspace = page.getByRole('region', { name: '데스크톱 메일 작업공간' });
await desktopWorkspace.getByLabel('Search emails').fill('출시');
await desktopWorkspace.getByRole('button', { name: '검색' }).click();
@@ -43,7 +43,7 @@ test('selects an email on mobile and executes visible detail task actions', asyn
await mockDashboardApi(page);
await page.goto('/');
- await page.getByRole('button', { name: '이메일 작업공간 열기' }).click();
+ await page.getByRole('button', { name: '메일함 바로가기' }).first().click();
await page.getByRole('button', { name: /김지현 PM/ }).click();
const detailRegion = page.getByRole('region', { name: '모바일 메일 상세' });
diff --git a/frontend/tests/e2e/live-smoke.spec.ts b/frontend/tests/e2e/live-smoke.spec.ts
index 37340f2cd..adefe6e2d 100644
--- a/frontend/tests/e2e/live-smoke.spec.ts
+++ b/frontend/tests/e2e/live-smoke.spec.ts
@@ -14,7 +14,7 @@ test.beforeEach(async ({ page }) => {
test('live dashboard renders seeded inbox through real HTTP', async ({ page }) => {
await page.goto('/');
- await expect(page.getByText('Naruon Mail')).toBeVisible();
+ await expect(page.getByRole('img', { name: 'Naruon' })).toBeVisible();
await expect(page.getByText('Live E2E Release')).toBeVisible({ timeout: 15_000 });
await expect(page.getByText('Failed to load emails.')).toHaveCount(0);
});
diff --git a/screenshot.mjs b/screenshot.mjs
new file mode 100644
index 000000000..74fe0aea1
--- /dev/null
+++ b/screenshot.mjs
@@ -0,0 +1,12 @@
+import { chromium } from '@playwright/test';
+
+(async () => {
+ const browser = await chromium.launch();
+ const page = await browser.newPage({ viewport: { width: 1280, height: 1024 } });
+ await page.goto('http://localhost:3000');
+ // Wait for network idle or a specific element
+ await page.waitForTimeout(2000);
+ await page.screenshot({ path: 'frontend/test-results/manual-screenshot.png', fullPage: true });
+ await browser.close();
+ console.log('Screenshot saved to frontend/test-results/manual-screenshot.png');
+})();