From ed8e92f1fefbb53af4bdaa2f491b7c54c758a4ef Mon Sep 17 00:00:00 2001
From: Jaeho
Date: Wed, 3 Jun 2026 22:18:43 +0900
Subject: [PATCH 1/3] =?UTF-8?q?feat(analysis):=20DocumentList=EC=97=90=20?=
=?UTF-8?q?=EC=86=8C=EC=8A=A4=20=EC=9C=A0=ED=98=95(sourceType)=20=ED=95=84?=
=?UTF-8?q?=ED=84=B0=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
이력서/레포 등 소스 유형별로 분석 결과를 분리 표시할 수 있도록
클라이언트 사이드 sourceType 필터와 소스별 빈 상태 문구를 추가.
Co-Authored-By: Claude Opus 4.8
---
.../src/features/analysis/ui/DocumentList.tsx | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
diff --git a/frontend/src/features/analysis/ui/DocumentList.tsx b/frontend/src/features/analysis/ui/DocumentList.tsx
index a5724168..c993a759 100644
--- a/frontend/src/features/analysis/ui/DocumentList.tsx
+++ b/frontend/src/features/analysis/ui/DocumentList.tsx
@@ -24,9 +24,11 @@ const SOURCE_LABEL: Record = {
type Props = {
filter?: DocumentFilter
+ // 클라이언트에서 소스 유형으로 한정 (탭별 분석 결과 분리용).
+ sourceType?: AnalysisSourceType
}
-export function DocumentList({ filter = {} }: Props) {
+export function DocumentList({ filter = {}, sourceType }: Props) {
const { data = [], isPending, isError, error } = useDocuments(filter)
if (isPending) {
@@ -39,12 +41,18 @@ export function DocumentList({ filter = {} }: Props) {
)
}
- if (data.length === 0) {
+
+ const docs = sourceType
+ ? data.filter((doc) => doc.sourceType === sourceType)
+ : data
+
+ if (docs.length === 0) {
+ const subject = sourceType ? SOURCE_LABEL[sourceType] : '이력서·레포'
return (
아직 분석된 문서가 없습니다.
- 이력서·레포 분석이 완료되면 요약과 기술 스택이 여기에 표시됩니다.
+ {subject} 분석이 완료되면 요약과 기술 스택이 여기에 표시됩니다.
)
@@ -52,7 +60,7 @@ export function DocumentList({ filter = {} }: Props) {
return (
- {data.map((doc) => (
+ {docs.map((doc) => (
))}
From 00b18c31ff7967b5621f1b9f2eb6cc8fac4a8267 Mon Sep 17 00:00:00 2001
From: Jaeho
Date: Wed, 3 Jun 2026 22:18:58 +0900
Subject: [PATCH 2/3] =?UTF-8?q?feat(workspace):=20=EC=9B=8C=ED=81=AC?=
=?UTF-8?q?=EC=8A=A4=ED=8E=98=EC=9D=B4=EC=8A=A4/=ED=9E=88=EC=8A=A4?=
=?UTF-8?q?=ED=86=A0=EB=A6=AC=EB=A5=BC=20=EC=82=AC=EC=9D=B4=EB=93=9C?=
=?UTF-8?q?=EB=B0=94=20=EB=8C=80=EC=8B=9C=EB=B3=B4=EB=93=9C=EB=A1=9C=20?=
=?UTF-8?q?=ED=86=B5=ED=95=A9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- workspace·history 페이지를 좌측 사이드바 단일 대시보드로 통합
- 사이드바 4탭: 홈/이력서/레포지토리/히스토리 (URL 기반 뷰 전환)
- 홈 뷰: 면접 시작 CTA + 바로가기 + 요약 통계
- 이력서·레포지토리 관리를 별도 탭으로 분리, 각 탭에 소스별 분석 결과 표시
- 모든 탭을 /workspace/* 경로로 통일, 구 /history는 리다이렉트
- workspace-sidebar 위젯 신설, 미사용 workspace-profile-card 제거
Co-Authored-By: Claude Opus 4.8
---
frontend/src/app/router/index.tsx | 24 ++-
frontend/src/pages/CLAUDE.md | 2 +-
frontend/src/pages/History/index.ts | 1 -
frontend/src/pages/History/ui/HistoryPage.tsx | 34 ---
.../src/pages/Workspace/ui/HistoryView.tsx | 26 +++
frontend/src/pages/Workspace/ui/HomeView.tsx | 103 +++++++++
frontend/src/pages/Workspace/ui/ReposView.tsx | 26 +++
.../src/pages/Workspace/ui/ResumesView.tsx | 24 +++
.../src/pages/Workspace/ui/WorkspacePage.tsx | 106 ++++-----
frontend/src/widgets/CLAUDE.md | 4 +-
.../widgets/workspace-profile-card/index.ts | 1 -
.../ui/WorkspaceProfileCard.tsx | 79 -------
.../src/widgets/workspace-sidebar/index.ts | 1 +
.../workspace-sidebar/ui/WorkspaceSidebar.tsx | 204 ++++++++++++++++++
14 files changed, 461 insertions(+), 174 deletions(-)
delete mode 100644 frontend/src/pages/History/index.ts
delete mode 100644 frontend/src/pages/History/ui/HistoryPage.tsx
create mode 100644 frontend/src/pages/Workspace/ui/HistoryView.tsx
create mode 100644 frontend/src/pages/Workspace/ui/HomeView.tsx
create mode 100644 frontend/src/pages/Workspace/ui/ReposView.tsx
create mode 100644 frontend/src/pages/Workspace/ui/ResumesView.tsx
delete mode 100644 frontend/src/widgets/workspace-profile-card/index.ts
delete mode 100644 frontend/src/widgets/workspace-profile-card/ui/WorkspaceProfileCard.tsx
create mode 100644 frontend/src/widgets/workspace-sidebar/index.ts
create mode 100644 frontend/src/widgets/workspace-sidebar/ui/WorkspaceSidebar.tsx
diff --git a/frontend/src/app/router/index.tsx b/frontend/src/app/router/index.tsx
index ffdc4f54..1cb05589 100644
--- a/frontend/src/app/router/index.tsx
+++ b/frontend/src/app/router/index.tsx
@@ -1,4 +1,4 @@
-import { createBrowserRouter } from 'react-router-dom'
+import { createBrowserRouter, Navigate } from 'react-router-dom'
import { RequireAuth } from '@/features/auth'
import HomePage from '@/pages/Home'
import LoginPage from '@/pages/Login'
@@ -7,7 +7,6 @@ import WorkspacePage from '@/pages/Workspace'
import InterviewSetupPage from '@/pages/InterviewSetup'
import InterviewSessionPage from '@/pages/InterviewSession'
import SessionFeedbackPage from '@/pages/SessionFeedback'
-import HistoryPage from '@/pages/History'
import SharedFeedbackPage from '@/pages/SharedFeedback'
export const router = createBrowserRouter([
@@ -23,6 +22,22 @@ export const router = createBrowserRouter([
),
},
+ {
+ path: '/workspace/resumes',
+ element: (
+
+
+
+ ),
+ },
+ {
+ path: '/workspace/repos',
+ element: (
+
+
+
+ ),
+ },
{
path: '/sessions/new',
element: (
@@ -48,13 +63,14 @@ export const router = createBrowserRouter([
),
},
{
- path: '/history',
+ path: '/workspace/history',
element: (
-
+
),
},
+ { path: '/history', element: },
{
path: '/design-system/*',
lazy: async () => {
diff --git a/frontend/src/pages/CLAUDE.md b/frontend/src/pages/CLAUDE.md
index f91c155c..6c4b58d0 100644
--- a/frontend/src/pages/CLAUDE.md
+++ b/frontend/src/pages/CLAUDE.md
@@ -40,7 +40,7 @@ pages/Workspace/
| `Workspace` | `/workspace` | `features/resume`, `features/repo` (도입 예정) |
| `Interview` | `/sessions/new`, `/sessions/:id` | `features/interview` |
| `Interview (Feedback)` | `/sessions/:id/feedback` | `features/feedback` |
-| `History` | `/history`, `/history/:id` | `features/history` (도입 예정) |
+| `History` | `/workspace/history` (구 `/history` → 리다이렉트) | `features/history` |
---
diff --git a/frontend/src/pages/History/index.ts b/frontend/src/pages/History/index.ts
deleted file mode 100644
index c77a6ed6..00000000
--- a/frontend/src/pages/History/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './ui/HistoryPage'
diff --git a/frontend/src/pages/History/ui/HistoryPage.tsx b/frontend/src/pages/History/ui/HistoryPage.tsx
deleted file mode 100644
index 4a2484cc..00000000
--- a/frontend/src/pages/History/ui/HistoryPage.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { SiteNav } from '@/widgets/site-nav'
-import { SiteFooter } from '@/widgets/site-footer'
-import {
- ScoreTrend,
- SessionHistoryList,
- StatsSummary,
- useUserStats,
-} from '@/features/history'
-
-export default function HistoryPage() {
- const { data: stats } = useUserStats()
-
- return (
-
-
-
- 면접 히스토리
-
- {stats && (
-
-
-
-
- )}
-
-
-
-
-
- )
-}
diff --git a/frontend/src/pages/Workspace/ui/HistoryView.tsx b/frontend/src/pages/Workspace/ui/HistoryView.tsx
new file mode 100644
index 00000000..a4ee6c67
--- /dev/null
+++ b/frontend/src/pages/Workspace/ui/HistoryView.tsx
@@ -0,0 +1,26 @@
+import {
+ ScoreTrend,
+ SessionHistoryList,
+ StatsSummary,
+ useUserStats,
+} from '@/features/history'
+
+export function HistoryView() {
+ const { data: stats } = useUserStats()
+
+ return (
+
+ {stats && (
+
+
+
+
+ )}
+
+
+
+ )
+}
diff --git a/frontend/src/pages/Workspace/ui/HomeView.tsx b/frontend/src/pages/Workspace/ui/HomeView.tsx
new file mode 100644
index 00000000..6cc1ec21
--- /dev/null
+++ b/frontend/src/pages/Workspace/ui/HomeView.tsx
@@ -0,0 +1,103 @@
+import { Link } from 'react-router-dom'
+import {
+ ScoreTrend,
+ StatsSummary,
+ useUserStats,
+} from '@/features/history'
+import { Button } from '@/shared/ui/Button'
+
+export function HomeView() {
+ const { data: stats } = useUserStats()
+ const hasSessions = (stats?.totalSessionCount ?? 0) > 0
+
+ return (
+
+
+
+ 맞춤 모의 면접
+
+
+ 이력서·레포 기반 맞춤 면접을 시작하세요
+
+
+ 면접 모드와 직군을 고르면 AI가 질문을 생성하고, 실시간으로 답변을
+ 주고받습니다.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {hasSessions && stats && (
+
+ )}
+
+ )
+}
+
+function QuickLink({
+ to,
+ title,
+ description,
+}: {
+ to: string
+ title: string
+ description: string
+}) {
+ return (
+
+
+
+ {title}
+
+
{description}
+
+
+ →
+
+
+ )
+}
diff --git a/frontend/src/pages/Workspace/ui/ReposView.tsx b/frontend/src/pages/Workspace/ui/ReposView.tsx
new file mode 100644
index 00000000..697fba99
--- /dev/null
+++ b/frontend/src/pages/Workspace/ui/ReposView.tsx
@@ -0,0 +1,26 @@
+import { WorkspaceSection } from '@/widgets/workspace-section'
+import { RepoList, RepoPicker } from '@/features/repo'
+import { DocumentList } from '@/features/analysis'
+
+export function ReposView() {
+ return (
+
+ )
+}
diff --git a/frontend/src/pages/Workspace/ui/ResumesView.tsx b/frontend/src/pages/Workspace/ui/ResumesView.tsx
new file mode 100644
index 00000000..ccb769cc
--- /dev/null
+++ b/frontend/src/pages/Workspace/ui/ResumesView.tsx
@@ -0,0 +1,24 @@
+import { WorkspaceSection } from '@/widgets/workspace-section'
+import { ResumeList, ResumeUploader } from '@/features/resume'
+import { DocumentList } from '@/features/analysis'
+
+export function ResumesView() {
+ return (
+
+ }
+ >
+
+
+
+
+
+
+
+ )
+}
diff --git a/frontend/src/pages/Workspace/ui/WorkspacePage.tsx b/frontend/src/pages/Workspace/ui/WorkspacePage.tsx
index dd1dec0e..84368e6f 100644
--- a/frontend/src/pages/Workspace/ui/WorkspacePage.tsx
+++ b/frontend/src/pages/Workspace/ui/WorkspacePage.tsx
@@ -1,64 +1,66 @@
-import { Link } from 'react-router-dom'
-import { SiteNav } from '@/widgets/site-nav'
-import { SiteFooter } from '@/widgets/site-footer'
-import { WorkspaceProfileCard } from '@/widgets/workspace-profile-card'
-import { WorkspaceSection } from '@/widgets/workspace-section'
-import { ResumeList, ResumeUploader } from '@/features/resume'
-import { RepoList, RepoPicker } from '@/features/repo'
-import { DocumentList } from '@/features/analysis'
-import { Button } from '@/shared/ui/Button'
+import { useLocation } from 'react-router-dom'
+import { useAuth } from '@/features/auth'
+import { WorkspaceSidebar } from '@/widgets/workspace-sidebar'
import { useWorkspaceAnalysisStream } from '../model/useWorkspaceAnalysisStream'
+import { HomeView } from './HomeView'
+import { ResumesView } from './ResumesView'
+import { ReposView } from './ReposView'
+import { HistoryView } from './HistoryView'
+
+type View = 'home' | 'resumes' | 'repos' | 'history'
+
+function resolveView(pathname: string): View {
+ if (pathname.startsWith('/workspace/history')) return 'history'
+ if (pathname.startsWith('/workspace/resumes')) return 'resumes'
+ if (pathname.startsWith('/workspace/repos')) return 'repos'
+ return 'home'
+}
export default function WorkspacePage() {
- // 분석 상태 실시간 구독 (SSE) — 완료되면 목록이 자동 갱신된다.
+ // 분석 상태 실시간 구독 (SSE) — 어떤 뷰에 있든 완료 시 목록이 자동 갱신된다.
useWorkspaceAnalysisStream()
- return (
-
-
-
-
-
-
-
-
- }
- >
-
- 면접 모드와 직군을 고르면 AI가 질문을 생성하고, 실시간으로 답변을 주고받습니다.
-
-
+ const { user } = useAuth()
+ const { pathname } = useLocation()
+ const view = resolveView(pathname)
- }
- >
-
-
+ const meta = {
+ home: {
+ title: user ? `안녕하세요, ${user.githubUsername}님` : '대시보드',
+ description: '오늘도 맞춤 모의 면접으로 실전 감각을 키워보세요.',
+ },
+ resumes: {
+ title: '이력서',
+ description: '이력서를 업로드하고 분석 결과를 확인하세요.',
+ },
+ repos: {
+ title: '레포지토리',
+ description: 'GitHub 레포를 등록하고 분석 결과를 확인하세요.',
+ },
+ history: {
+ title: '면접 히스토리',
+ description: '지난 면접 기록과 점수 추이를 확인하세요.',
+ },
+ }[view]
-
-
-
-
-
-
+ return (
+
+
+
+
+
+
+ {meta.title}
+
+ {meta.description}
+
-
-
-
+ {view === 'home' &&
}
+ {view === 'resumes' &&
}
+ {view === 'repos' &&
}
+ {view === 'history' &&
}
+
-
)
}
diff --git a/frontend/src/widgets/CLAUDE.md b/frontend/src/widgets/CLAUDE.md
index ad23c576..98aafc5a 100644
--- a/frontend/src/widgets/CLAUDE.md
+++ b/frontend/src/widgets/CLAUDE.md
@@ -93,8 +93,8 @@ widgets/{X} → pages/*, app/* ✗
| `home-quote` | 다크 그린 quote · 팀 크레딧 | `/` |
| `home-faq` | FAQ 아코디언 (`` 기반) | `/` |
| `home-cta` | 풀-블리드 CTA 배너 | `/` |
-| `workspace-profile-card` | `useAuth().user` 기반 프로필 카드 (avatar + 핸들 + email + 연결 상태) | `/workspace` |
-| `workspace-section` | 타이틀·설명·우측 액션·children 슬롯 컨테이너 (도메인 무관) | `/workspace` |
+| `workspace-section` | 타이틀·설명·우측 액션·children 슬롯 컨테이너 (도메인 무관) | `/workspace/resumes`, `/workspace/repos` |
+| `workspace-sidebar` | 대시보드 좌측 사이드바 (브랜드 + 홈/이력서/레포지토리/히스토리 `NavLink` 전환 + 프로필·로그아웃). `lg` 미만에서는 상단 가로 바 | `/workspace`, `/workspace/resumes`, `/workspace/repos`, `/workspace/history` |
---
diff --git a/frontend/src/widgets/workspace-profile-card/index.ts b/frontend/src/widgets/workspace-profile-card/index.ts
deleted file mode 100644
index 4f004789..00000000
--- a/frontend/src/widgets/workspace-profile-card/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { WorkspaceProfileCard } from './ui/WorkspaceProfileCard'
diff --git a/frontend/src/widgets/workspace-profile-card/ui/WorkspaceProfileCard.tsx b/frontend/src/widgets/workspace-profile-card/ui/WorkspaceProfileCard.tsx
deleted file mode 100644
index 77754f28..00000000
--- a/frontend/src/widgets/workspace-profile-card/ui/WorkspaceProfileCard.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-import { useAuth } from '@/features/auth'
-
-export function WorkspaceProfileCard() {
- const { status, user } = useAuth()
-
- if (status === 'loading') return
- if (!user) return null
-
- return (
-
-
-
-
- @{user.githubUsername}
-
-
- {user.email ?? '이메일 미공개'}
-
-
-
- GitHub로 연결됨
-
-
-
- )
-}
-
-function Avatar({ url, name }: { url: string | null; name: string }) {
- if (url) {
- return (
-
- )
- }
- const initial = name.charAt(0).toUpperCase() || '?'
- return (
-
- {initial}
-
- )
-}
-
-function GithubMark() {
- return (
-
- )
-}
-
-function ProfileSkeleton() {
- return (
-
- )
-}
diff --git a/frontend/src/widgets/workspace-sidebar/index.ts b/frontend/src/widgets/workspace-sidebar/index.ts
new file mode 100644
index 00000000..907a994a
--- /dev/null
+++ b/frontend/src/widgets/workspace-sidebar/index.ts
@@ -0,0 +1 @@
+export { WorkspaceSidebar } from './ui/WorkspaceSidebar'
diff --git a/frontend/src/widgets/workspace-sidebar/ui/WorkspaceSidebar.tsx b/frontend/src/widgets/workspace-sidebar/ui/WorkspaceSidebar.tsx
new file mode 100644
index 00000000..9e15d0ee
--- /dev/null
+++ b/frontend/src/widgets/workspace-sidebar/ui/WorkspaceSidebar.tsx
@@ -0,0 +1,204 @@
+import type { ReactNode } from 'react'
+import { Link, NavLink } from 'react-router-dom'
+import { useAuth, useLogout } from '@/features/auth'
+
+type NavItem = {
+ to: string
+ label: string
+ icon: ReactNode
+ end?: boolean
+}
+
+const navItems: NavItem[] = [
+ { to: '/workspace', label: '홈', icon: , end: true },
+ { to: '/workspace/resumes', label: '이력서', icon: },
+ { to: '/workspace/repos', label: '레포지토리', icon: },
+ { to: '/workspace/history', label: '히스토리', icon: },
+]
+
+export function WorkspaceSidebar() {
+ const { user } = useAuth()
+ const { logout, loggingOut } = useLogout()
+
+ return (
+
+ )
+}
+
+function HomeIcon() {
+ return (
+
+ )
+}
+
+function ResumeIcon() {
+ return (
+
+ )
+}
+
+function RepoIcon() {
+ return (
+
+ )
+}
+
+function HistoryIcon() {
+ return (
+
+ )
+}
+
+function LogoutIcon() {
+ return (
+
+ )
+}
From ee8e6d34f3c8af410ed170864785087a4c18ad4c Mon Sep 17 00:00:00 2001
From: Jaeho
Date: Wed, 3 Jun 2026 22:19:05 +0900
Subject: [PATCH 3/3] =?UTF-8?q?refactor(site-nav):=20=EB=A1=9C=EA=B7=B8?=
=?UTF-8?q?=EC=9D=B8=20=EC=8B=9C=20=ED=97=A4=EB=8D=94=EB=A5=BC=20=ED=94=84?=
=?UTF-8?q?=EB=A1=9C=ED=95=84=20=EB=8B=A8=EC=9D=BC=20=EC=A7=84=EC=9E=85?=
=?UTF-8?q?=EC=A0=90=EC=9C=BC=EB=A1=9C=20=EC=A0=95=EB=A6=AC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
대시보드 사이드바 도입으로 중복된 히스토리 링크를 제거하고,
인증 시 헤더에는 프로필(→/workspace)과 로그아웃만 남김.
Co-Authored-By: Claude Opus 4.8
---
frontend/src/widgets/site-nav/ui/SiteNav.tsx | 6 ------
1 file changed, 6 deletions(-)
diff --git a/frontend/src/widgets/site-nav/ui/SiteNav.tsx b/frontend/src/widgets/site-nav/ui/SiteNav.tsx
index de6b7d68..989b617e 100644
--- a/frontend/src/widgets/site-nav/ui/SiteNav.tsx
+++ b/frontend/src/widgets/site-nav/ui/SiteNav.tsx
@@ -53,12 +53,6 @@ export function SiteNav() {
{status === 'authenticated' ? (
<>
-
- 히스토리
-