Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/components/layout/AppLayout.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -189,4 +205,12 @@
padding: 16px;
overflow-y: visible;
}

.contentWorkInbox {
padding-bottom: 16px;
}

.contentDashboard {
padding-top: 16px;
}
}
3 changes: 2 additions & 1 deletion src/components/layout/AppLayout.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,15 @@ 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()

expect(screen.getByText('FOWOCO')).toBeInTheDocument()
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 () => {
Expand Down
8 changes: 7 additions & 1 deletion src/components/layout/AppLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -78,7 +80,11 @@ export function AppLayout() {
</div>
</header>

<main className={styles.content}>
<main
className={`${styles.content} ${isWorkInbox ? styles.contentWorkInbox : ''} ${
isDashboard ? styles.contentDashboard : ''
}`}
>
<RouteTransition />
</main>
</div>
Expand Down
6 changes: 2 additions & 4 deletions src/components/layout/navItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,19 @@ 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
to: string
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 },
]
35 changes: 31 additions & 4 deletions src/components/ui/SearchInput/SearchInput.module.css
Original file line number Diff line number Diff line change
@@ -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;
}
18 changes: 11 additions & 7 deletions src/components/ui/SearchInput/SearchInput.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ChangeEvent } from 'react'
import searchIcon from './search.svg'
import styles from './SearchInput.module.css'

export interface SearchInputProps {
Expand All @@ -21,12 +22,15 @@ export function SearchInput({
}

return (
<input
className={`${styles.search} ${className ?? ''}`}
placeholder={placeholder}
value={value}
onChange={handleChange}
aria-label={ariaLabel}
/>
<label className={`${styles.field} ${className ?? ''}`}>
<img className={styles.icon} src={searchIcon} alt="" aria-hidden="true" />
<input
className={styles.search}
placeholder={placeholder}
value={value}
onChange={handleChange}
aria-label={ariaLabel}
/>
</label>
)
}
3 changes: 3 additions & 0 deletions src/components/ui/SearchInput/search.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
87 changes: 81 additions & 6 deletions src/components/ui/WorkItemRow/WorkItemRow.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -36,7 +38,7 @@
.rail {
flex-shrink: 0;
width: 4px;
height: 44px;
height: 40px;
border-radius: var(--fowoco-radius-4);
background: var(--status-warning);
}
Expand All @@ -45,6 +47,10 @@
background: var(--status-critical);
}

.railInfo {
background: #3976d3;
}

.railNeutral {
background: var(--border-default);
}
Expand All @@ -53,15 +59,57 @@
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: var(--fowoco-spacing-4);
align-items: center;
gap: 12px;
}

.title {
font-family: var(--font-sans);
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 {
Expand All @@ -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%;
}
}
36 changes: 33 additions & 3 deletions src/components/ui/WorkItemRow/WorkItemRow.tsx
Original file line number Diff line number Diff line change
@@ -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<WorkItemUrgency, string> = {
warning: '',
critical: styles.railCritical,
info: styles.railInfo,
neutral: styles.railNeutral,
}

export type WorkItemStatusTone = 'warning' | 'primary' | 'neutral'

const STATUS_CLASS: Record<WorkItemStatusTone, string> = {
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
Expand All @@ -19,6 +31,9 @@ export interface WorkItemRowProps {
export function WorkItemRow({
title,
meta,
statusLabel,
statusTone = 'neutral',
detailItems = [],
nextAction,
urgency = 'warning',
onClick,
Expand All @@ -28,7 +43,22 @@ export function WorkItemRow({
<span className={`${styles.rail} ${RAIL_CLASS[urgency]}`} aria-hidden="true" />
<span className={styles.content}>
<span className={styles.title}>{title}</span>
<span className={styles.meta}>{meta}</span>
{statusLabel || detailItems.length > 0 ? (
<span className={styles.inlineMeta}>
{statusLabel && (
<span className={`${styles.status} ${STATUS_CLASS[statusTone]}`}>
{statusLabel}
</span>
)}
{detailItems.map((item) => (
<span key={item} className={styles.detailItem}>
{item}
</span>
))}
</span>
) : (
<span className={styles.meta}>{meta}</span>
)}
</span>
<span className={styles.next}>{nextAction}</span>
</button>
Expand Down
Loading