diff --git a/src/components/layout/AppLayout.module.css b/src/components/layout/AppLayout.module.css index 6fe7c35..52d2021 100644 --- a/src/components/layout/AppLayout.module.css +++ b/src/components/layout/AppLayout.module.css @@ -18,7 +18,7 @@ flex: 0 0 208px; display: flex; flex-direction: column; - padding: 28px 20px; + padding: 28px 20px 22px; overflow-y: auto; background: var(--brand-dark); color: var(--fowoco-white); @@ -44,34 +44,50 @@ } .brand { - margin: 0 0 4px 4px; + height: 32px; + margin: 0 0 0 4px; font-size: 20px; font-weight: 700; + line-height: 28px; } .kicker { - margin: 0 0 24px 4px; + height: 19px; + margin: 20px 0 19px 4px; font-size: 11px; font-weight: 500; + line-height: 19px; color: var(--fowoco-teal-100); } .nav { display: flex; flex-direction: column; - gap: 4px; + gap: 8px; } .navLink { - padding: 10px 16px; + position: relative; + display: flex; + align-items: center; + gap: 12px; + height: 44px; + padding: 0 16px; border-radius: var(--fowoco-radius-6); font-size: 14px; color: var(--fowoco-teal-100); text-decoration: none; - transition: background-color var(--fowoco-motion-fast) var(--fowoco-easing-standard), + transition: + background-color var(--fowoco-motion-fast) var(--fowoco-easing-standard), color var(--fowoco-motion-fast) var(--fowoco-easing-standard); } +.navIcon { + width: 18px; + height: 18px; + flex: 0 0 18px; +} + .navLink:hover { background: rgba(255, 255, 255, 0.06); color: var(--fowoco-white); @@ -83,6 +99,17 @@ font-weight: 500; } +.navLinkActive::before { + position: absolute; + top: 10px; + bottom: 10px; + left: 0; + width: 3px; + border-radius: 2px; + background: var(--brand-primary); + content: ''; +} + .sidebarFooter { margin-top: auto; display: flex; @@ -90,8 +117,7 @@ gap: 2px; } -.help, -.logout { +.help { padding: 10px 16px; background: none; border: none; @@ -101,11 +127,6 @@ cursor: pointer; } -.logout { - color: var(--fowoco-white); - opacity: 0.85; -} - .main { flex: 1; display: flex; @@ -124,7 +145,7 @@ .topBar { display: flex; align-items: center; - justify-content: flex-end; + justify-content: space-between; gap: 12px; flex-shrink: 0; min-height: 64px; @@ -133,16 +154,27 @@ border-bottom: 1px solid var(--border-default); } +.topBarBack { + font-size: 13px; + color: var(--text-secondary); + text-decoration: none; +} + +.topBarBack:hover { + color: var(--text-primary); +} + .topBarActions { display: flex; align-items: center; gap: 16px; + margin-left: auto; } .content { flex: 1; min-height: 0; - padding: 32px; + padding: 32px 40px; overflow-y: auto; } diff --git a/src/components/layout/AppLayout.tsx b/src/components/layout/AppLayout.tsx index 570cf06..3fd9957 100644 --- a/src/components/layout/AppLayout.tsx +++ b/src/components/layout/AppLayout.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react' -import { NavLink, useNavigate } from 'react-router-dom' +import { Link, NavLink, useLocation, useNavigate } from 'react-router-dom' import { OnboardingTour } from '../onboarding/OnboardingTour' import { hasCompletedOnboarding, markOnboardingCompleted } from '../onboarding/onboardingStorage' import { useAuthStore } from '../../store/authStore' @@ -12,10 +12,12 @@ import { RouteTransition } from './RouteTransition' export function AppLayout() { const navigate = useNavigate() + const location = useLocation() const user = useAuthStore((state) => state.user) const logout = useAuthStore((state) => state.logout) const [helpOpen, setHelpOpen] = useState(false) const [tourOpen, setTourOpen] = useState(false) + const isTaskDetail = /^\/tasks\/[^/]+$/.test(location.pathname) useEffect(() => { if (!hasCompletedOnboarding()) setTourOpen(true) @@ -51,7 +53,8 @@ export function AppLayout() { `${styles.navLink} ${isActive ? styles.navLinkActive : ''}` } > - {item.label} + + {item.label} ))} @@ -60,14 +63,16 @@ export function AppLayout() { -
{title}
- ) : ( -FOWOCO
- )} - {expired ? 만료 : right} + {title ?{title}
:FOWOCO
} + {expired ? ( + 만료 + ) : right ? ( + {right} + ) : null}+ {taskDue.display} · {TASK_SOURCE_LABEL[task.source]} · {TASK_STATUS_LABEL[task.status]} +
+- {taskDue.display} · {task.workflow_id} -
-{CONTEXT_ACCESS.label}
+Agent가 참고한 정보
- {CONTEXT_ACCESS.rows.map((row) => (
-
- {row.label} {row.value}
-
-
- ))}
+ 출처 · {TASK_SOURCE_LABEL[task.source]}
+
+ 필수 항목 · {completedRequiredChecklist}/{requiredChecklist.length}
+
+ 필수 정보 ·{' '}
+ {informationReady ? '확인' : `${task.missing_required_slots.length}개 부족`}
+
+ 필요 서류 · {documentsReady ? '확인' : '보완 필요'}
- 서버에 저장된 현재 Task와 체크리스트만 표시합니다. 고정된 예시 단계는 사용하지 않습니다. -
-현재 · {TASK_STATUS_LABEL[task.status]}
이 업무에 등록된 진행 항목이 없습니다.
+ ) : ( +{item.label}
++ {item.required ? '필수 항목' : '선택 항목'} +
+현재 서버 상태와 필수 조건을 기준으로 확인합니다.
+현재 진행을 막는 조건을 먼저 확인하세요.
완료 처리되었습니다.
) : (- 완료 처리 불가 · {completionBlockers.join(' · ') || '현재 상태 확인 필요'} + 완료 처리 불가 · {completionBlockers.join(' · ') || '현재 상태 확인 필요'} 확인이 + 필요합니다.
)}- 승인·반려·완료 결과는 서버 응답 후 Task를 다시 조회해 반영합니다. 화면에서 성공 상태를 임의로 만들지 않습니다. + {approvalReady + ? '실제 전달과 외부 제출은 담당자가 직접 수행하고 결과를 증빙으로 남깁니다.' + : '승인 전에는 근로자 링크 전달이나 외부 처리를 시작할 수 없습니다.'}
링크 상태 · 만료
이 링크는 만료됐거나 새 링크 발급으로 폐기되었습니다.
- {token ? <>
기존 링크로는 제출할 수 없습니다.> : null}
+ {token ? (
+ <>
+
+ 기존 링크로는 제출할 수 없습니다.
+ >
+ ) : null}
담당자에게 요청하는 방법
한빛정밀 인사팀 · 김경민
+회사 인사팀 담당자
기존에 안내를 받은 문자 또는 메신저로 요청하세요.
새 링크가 발급되면 이전 링크는 즉시 폐기됩니다.
-
- 이 버튼은 메시지를 자동 발송하지 않습니다.
+
이 버튼은 메시지를 자동 발송하지 않습니다.
이 화면을 닫아도 같은 링크로 다시 열 수 있습니다.
서류 제출 요청
+회사 인사팀 요청
담당자에게 질문 의사를 전했습니다.
} - {responseError &&{responseError}
} + {responseError && ( ++ {responseError} +
+ )}제출 결과는 담당자에게 전달되며 같은 요청의 중복 제출은 차단됩니다.
+다음 화면에서 요청받은 파일을 선택해 제출할 수 있습니다.
) } diff --git a/src/pages/LinkUploadPage/LinkUploadPage.module.css b/src/pages/LinkUploadPage/LinkUploadPage.module.css index 168dce9..9c40bc9 100644 --- a/src/pages/LinkUploadPage/LinkUploadPage.module.css +++ b/src/pages/LinkUploadPage/LinkUploadPage.module.css @@ -1,5 +1,5 @@ .headline { - margin: 30px 0 0; + margin: 6px 0 0; font-size: 26px; font-weight: 700; line-height: 36px; @@ -7,7 +7,7 @@ } .subtext { - margin: 16px 0 0; + margin: 12px 0 0; font-size: 15px; line-height: 25px; color: var(--text-secondary); @@ -15,7 +15,8 @@ .dropzone { width: 100%; - margin-top: 30px; + min-height: 148px; + margin-top: 20px; padding: 20px; background: var(--surface-default); border: 2px solid var(--brand-primary); @@ -108,7 +109,8 @@ display: flex; align-items: center; justify-content: space-between; - margin-top: 24px; + min-height: 72px; + margin-top: 16px; padding: 12px 16px; background: var(--fowoco-green-50); border-radius: var(--fowoco-radius-8); @@ -137,7 +139,7 @@ } .helpLabel { - margin: 20px 0 0; + margin: 24px 0 0; font-size: 13px; font-weight: 500; color: var(--text-secondary); @@ -146,7 +148,7 @@ .helpLinks { display: flex; flex-direction: column; - gap: 12px; + gap: 8px; margin-top: 12px; } @@ -177,7 +179,7 @@ .submit { width: 100%; height: 52px; - margin-top: 24px; + margin-top: 32px; background: var(--brand-primary); border: none; border-radius: var(--fowoco-radius-6); diff --git a/src/pages/LinkUploadPage/LinkUploadPage.tsx b/src/pages/LinkUploadPage/LinkUploadPage.tsx index 15a5836..db17880 100644 --- a/src/pages/LinkUploadPage/LinkUploadPage.tsx +++ b/src/pages/LinkUploadPage/LinkUploadPage.tsx @@ -86,7 +86,9 @@ export function LinkUploadPage() { navigate(`/worker-portal/${encodeURIComponent(token)}/expired`, { replace: true }) return } - setFileError(caught instanceof ApiError ? getErrorMessage(caught) : '파일을 제출하지 못했습니다.') + setFileError( + caught instanceof ApiError ? getErrorMessage(caught) : '파일을 제출하지 못했습니다.', + ) } finally { setSubmitting(false) } @@ -103,7 +105,9 @@ export function LinkUploadPage() { }) setResponseMessage(successMessage) } catch (caught) { - setFileError(caught instanceof ApiError ? getErrorMessage(caught) : '응답을 보내지 못했습니다.') + setFileError( + caught instanceof ApiError ? getErrorMessage(caught) : '응답을 보내지 못했습니다.', + ) } finally { setSubmitting(false) } @@ -112,7 +116,11 @@ export function LinkUploadPage() { if (!token) { return (여권 사진면 전체가 보이고 글자가 흐리지 않은지 확인해 주세요.
++ 여권 사진면 전체가 보이고 글자가 흐리지 않은지 확인해 주세요. +
{fileError}
} + {fileError && ( ++ {fileError} +
+ )} {responseMessage &&{responseMessage}
} {file && ({file.name}
-- {formatFileSize(file.size)} · 제출 전 -
+{formatFileSize(file.size)} · 제출 전 확인
- 업로드가 끝난 뒤 제출 응답까지 접수되어야 담당자 화면에 반영됩니다. -
+제출한 파일은 회사 인사팀 담당자가 확인합니다.
) } diff --git a/src/pages/WorkListPage/WorkInboxDetail.tsx b/src/pages/WorkListPage/WorkInboxDetail.tsx index adcb0ea..a5a4927 100644 --- a/src/pages/WorkListPage/WorkInboxDetail.tsx +++ b/src/pages/WorkListPage/WorkInboxDetail.tsx @@ -33,7 +33,8 @@ export function WorkInboxDetail({ group, onOpenTask }: WorkInboxDetailProps) { const progress = getWorkInboxCaseProgress(activeCase) const due = getDuePresentation(activeTask.task.due_date) const activeStatus = getTaskStatusPresentation(activeTask.task.status) - const headerStatus = due.tone === 'critical' ? { label: '긴급', tone: 'critical' as const } : activeStatus + const headerStatus = + due.tone === 'critical' ? { label: '긴급', tone: 'critical' as const } : activeStatus const reviewTasks = group.tasks.filter((item) => isReviewTask(item.task.status)) const detailTitleId = `work-inbox-detail-${group.worker.worker_id}` @@ -60,8 +61,12 @@ export function WorkInboxDetail({ group, onOpenTask }: WorkInboxDetailProps) {+ Agent 제안 · {due.label}, {getWorkflowLabel(activeTask)} 확인 필요 +
+우선 Case {Math.max(activeCaseIndex, 0) + 1}/{group.cases.length}
@@ -73,27 +78,29 @@ export function WorkInboxDetail({ group, onOpenTask }: WorkInboxDetailProps) { Case 열기 →Case {activeCase.caseId}
} -- {getWorkflowLabel(activeTask)} · {due.label} · {activeStatus.label} -
-Case {activeCase.caseId}
} ++ {getWorkflowLabel(activeTask)} · {due.label} · {activeStatus.label} +
+{getDecisionSummary(activeTask.task.status)}
++ 진행 업무 건 {group.cases.length}개 · 확인할 업무 {reviewTasks.length}개 · 자동 확정되지 + 않음 +
{getDecisionSummary(activeTask.task.status)}
-- 진행 Case {group.cases.length}개 · 확인할 업무 {reviewTasks.length}개 -
-- 판단 근거와 문서 연결 정보는 현재 API에서 제공되지 않습니다. -
) diff --git a/src/pages/WorkListPage/WorkInboxTargetList.tsx b/src/pages/WorkListPage/WorkInboxTargetList.tsx index 0dcbb88..5460abf 100644 --- a/src/pages/WorkListPage/WorkInboxTargetList.tsx +++ b/src/pages/WorkListPage/WorkInboxTargetList.tsx @@ -77,7 +77,6 @@ export function WorkInboxTargetList({ {group.worker.display_name}