diff --git a/src/components/layout/AppLayout.module.css b/src/components/layout/AppLayout.module.css index 52d2021..5d1a894 100644 --- a/src/components/layout/AppLayout.module.css +++ b/src/components/layout/AppLayout.module.css @@ -178,6 +178,22 @@ overflow-y: auto; } +.contentDashboard { + padding-top: 18px; +} + +.contentWorkInbox { + padding-bottom: 0; + overflow: hidden; +} + +@media (max-width: 1180px) { + .contentWorkInbox { + padding-bottom: 32px; + overflow-y: auto; + } +} + @media (max-width: 768px) { .topBar { padding: 12px 16px; @@ -189,4 +205,12 @@ padding: 16px; overflow-y: visible; } + + .contentWorkInbox { + padding-bottom: 16px; + } + + .contentDashboard { + padding-top: 16px; + } } diff --git a/src/components/layout/AppLayout.test.tsx b/src/components/layout/AppLayout.test.tsx index 502fcea..d17708d 100644 --- a/src/components/layout/AppLayout.test.tsx +++ b/src/components/layout/AppLayout.test.tsx @@ -35,7 +35,7 @@ afterEach(() => { }) describe('AppLayout', () => { - it('renders the FOWOCO logo and every nav item', () => { + it('renders the current global navigation without a workers tab', () => { localStorage.setItem('fowoco.onboarding.completed', 'true') renderLayout() @@ -43,6 +43,7 @@ describe('AppLayout', () => { for (const item of NAV_ITEMS) { expect(screen.getByRole('link', { name: item.label })).toBeInTheDocument() } + expect(screen.queryByRole('link', { name: '근로자' })).not.toBeInTheDocument() }) it('opens and closes the help modal', async () => { diff --git a/src/components/layout/AppLayout.tsx b/src/components/layout/AppLayout.tsx index 3fd9957..9e9dabc 100644 --- a/src/components/layout/AppLayout.tsx +++ b/src/components/layout/AppLayout.tsx @@ -18,6 +18,8 @@ export function AppLayout() { const [helpOpen, setHelpOpen] = useState(false) const [tourOpen, setTourOpen] = useState(false) const isTaskDetail = /^\/tasks\/[^/]+$/.test(location.pathname) + const isWorkInbox = location.pathname === '/tasks' + const isDashboard = location.pathname === '/dashboard' useEffect(() => { if (!hasCompletedOnboarding()) setTourOpen(true) @@ -78,7 +80,11 @@ export function AppLayout() { -
+
diff --git a/src/components/layout/navItems.ts b/src/components/layout/navItems.ts index c48ece5..c6c56fa 100644 --- a/src/components/layout/navItems.ts +++ b/src/components/layout/navItems.ts @@ -2,7 +2,6 @@ import documentsIcon from './nav-icons/documents.svg' import settingsIcon from './nav-icons/settings.svg' import todayIcon from './nav-icons/today.svg' import workIcon from './nav-icons/work.svg' -import workersIcon from './nav-icons/workers.svg' export interface NavItem { label: string @@ -10,13 +9,12 @@ export interface NavItem { iconSrc: string } -// Figma PWF v3 사이드바(Aside - SideNavBar, 05_Desktop Core Product 전 화면 공통)는 5개 메뉴만 -// 정의한다. Agent/티켓 메뉴는 Figma에 없는 화면이라(#206) 링크만 제거하고 라우트는 유지한다. +// 공용 사이드바에는 주요 운영 진입점만 노출한다. 근로자 기능은 업무·문서 흐름에서 +// 컨텍스트로 진입하므로 전역 탭에서는 제외하고, 딥링크와 관련 라우트는 유지한다. // SettingsPage는 제거되어 "설정" 메뉴는 내 프로필 페이지로 연결된다. export const NAV_ITEMS: NavItem[] = [ { label: 'Today', to: '/dashboard', iconSrc: todayIcon }, { label: '업무함', to: '/tasks', iconSrc: workIcon }, - { label: '근로자', to: '/workers', iconSrc: workersIcon }, { label: '문서함', to: '/documents', iconSrc: documentsIcon }, { label: '설정', to: '/profile', iconSrc: settingsIcon }, ] diff --git a/src/components/ui/SearchInput/SearchInput.module.css b/src/components/ui/SearchInput/SearchInput.module.css index 434632f..7104eb1 100644 --- a/src/components/ui/SearchInput/SearchInput.module.css +++ b/src/components/ui/SearchInput/SearchInput.module.css @@ -1,15 +1,42 @@ -.search { +.field { + display: flex; + align-items: center; + gap: 7px; flex: 1 1 240px; max-width: 520px; height: 48px; padding: 0 var(--fowoco-spacing-16); - font-size: 14px; - font-family: inherit; + overflow: hidden; + background: var(--surface-default); border: 1px solid var(--border-default); border-radius: var(--fowoco-radius-6); } -.search:focus-visible { +.field:focus-within { outline: 2px solid var(--brand-primary); outline-offset: 2px; } + +.icon { + flex: 0 0 16px; + width: 16px; + height: 16px; +} + +.search { + min-width: 0; + width: 100%; + height: 100%; + padding: 0; + background: transparent; + border: 0; + outline: 0; + font-family: inherit; + font-size: 14px; + color: var(--text-primary); +} + +.search::placeholder { + color: var(--text-secondary); + opacity: 1; +} diff --git a/src/components/ui/SearchInput/SearchInput.tsx b/src/components/ui/SearchInput/SearchInput.tsx index e7162aa..d619e42 100644 --- a/src/components/ui/SearchInput/SearchInput.tsx +++ b/src/components/ui/SearchInput/SearchInput.tsx @@ -1,4 +1,5 @@ import type { ChangeEvent } from 'react' +import searchIcon from './search.svg' import styles from './SearchInput.module.css' export interface SearchInputProps { @@ -21,12 +22,15 @@ export function SearchInput({ } return ( - + ) } diff --git a/src/components/ui/SearchInput/search.svg b/src/components/ui/SearchInput/search.svg new file mode 100644 index 0000000..5cdb1c6 --- /dev/null +++ b/src/components/ui/SearchInput/search.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/ui/WorkItemRow/WorkItemRow.module.css b/src/components/ui/WorkItemRow/WorkItemRow.module.css index 0a54620..38ba993 100644 --- a/src/components/ui/WorkItemRow/WorkItemRow.module.css +++ b/src/components/ui/WorkItemRow/WorkItemRow.module.css @@ -2,7 +2,9 @@ display: flex; align-items: center; gap: var(--fowoco-spacing-16); - padding: var(--fowoco-spacing-16) var(--fowoco-spacing-20); + width: 100%; + min-height: 56px; + padding: 8px var(--fowoco-spacing-20); background: var(--surface-default); border: 1px solid var(--border-default); border-radius: var(--fowoco-radius-8); @@ -36,7 +38,7 @@ .rail { flex-shrink: 0; width: 4px; - height: 44px; + height: 40px; border-radius: var(--fowoco-radius-4); background: var(--status-warning); } @@ -45,6 +47,10 @@ background: var(--status-critical); } +.railInfo { + background: #3976d3; +} + .railNeutral { background: var(--border-default); } @@ -53,8 +59,8 @@ flex: 1; min-width: 0; display: flex; - flex-direction: column; - gap: var(--fowoco-spacing-4); + align-items: center; + gap: 12px; } .title { @@ -62,6 +68,48 @@ font-size: 14px; font-weight: 500; color: var(--text-primary); + white-space: nowrap; +} + +.inlineMeta { + display: flex; + align-items: center; + gap: 8px; + min-width: 0; +} + +.status { + display: inline-flex; + align-items: center; + min-height: 26px; + padding: 4px 8px; + border-radius: var(--fowoco-radius-999); + font-size: 12px; + font-weight: 500; + line-height: 18px; + white-space: nowrap; +} + +.statusWarning { + color: var(--status-warning); + background: var(--fowoco-amber-50); +} + +.statusPrimary { + color: var(--brand-primary); + background: var(--fowoco-teal-50); +} + +.statusNeutral { + color: var(--text-secondary); + background: var(--surface-subtle); +} + +.detailItem { + font-size: 12px; + line-height: 18px; + color: var(--text-secondary); + white-space: nowrap; } .meta { @@ -72,8 +120,35 @@ .next { flex-shrink: 0; + display: inline-flex; + align-items: center; + justify-content: center; + width: 120px; + height: 40px; + padding: 8px 16px; + border: 1px solid var(--border-default); + border-radius: var(--fowoco-radius-6); font-family: var(--font-sans); - font-size: 13px; + font-size: 14px; font-weight: 500; - color: var(--brand-primary); + color: var(--text-primary); + background: var(--surface-default); +} + +@media (max-width: 720px) { + .content { + flex: 1 1 calc(100% - 20px); + align-items: flex-start; + flex-direction: column; + gap: 4px; + } + + .inlineMeta { + flex-wrap: wrap; + } + + .next { + flex-basis: auto; + width: 100%; + } } diff --git a/src/components/ui/WorkItemRow/WorkItemRow.tsx b/src/components/ui/WorkItemRow/WorkItemRow.tsx index ba56a8e..1505b31 100644 --- a/src/components/ui/WorkItemRow/WorkItemRow.tsx +++ b/src/components/ui/WorkItemRow/WorkItemRow.tsx @@ -1,16 +1,28 @@ import styles from './WorkItemRow.module.css' -export type WorkItemUrgency = 'warning' | 'critical' | 'neutral' +export type WorkItemUrgency = 'warning' | 'critical' | 'info' | 'neutral' const RAIL_CLASS: Record = { warning: '', critical: styles.railCritical, + info: styles.railInfo, neutral: styles.railNeutral, } +export type WorkItemStatusTone = 'warning' | 'primary' | 'neutral' + +const STATUS_CLASS: Record = { + warning: styles.statusWarning, + primary: styles.statusPrimary, + neutral: styles.statusNeutral, +} + export interface WorkItemRowProps { title: string - meta: string + meta?: string + statusLabel?: string + statusTone?: WorkItemStatusTone + detailItems?: string[] nextAction: string urgency?: WorkItemUrgency onClick?: () => void @@ -19,6 +31,9 @@ export interface WorkItemRowProps { export function WorkItemRow({ title, meta, + statusLabel, + statusTone = 'neutral', + detailItems = [], nextAction, urgency = 'warning', onClick, @@ -28,7 +43,22 @@ export function WorkItemRow({