From 6a9fc06f0e921fc9c956198d15c5c52c869f7c4e Mon Sep 17 00:00:00 2001 From: johngreen Date: Tue, 12 May 2026 08:07:15 +0900 Subject: [PATCH] =?UTF-8?q?feat(=EB=8C=80=EB=AC=B4=EC=9E=90):=20=ED=94=84?= =?UTF-8?q?=EB=A1=A0=ED=8A=B8=EC=97=94=EB=93=9C=20UI=20=E2=80=94=20UserFor?= =?UTF-8?q?mModal=20=EB=8C=80=EB=AC=B4=EC=9E=90=20=EC=84=B9=EC=85=98=20+?= =?UTF-8?q?=20ProfileModal=20=EC=A1=B0=ED=9A=8C=20+=20=EA=B2=B0=EC=9E=AC?= =?UTF-8?q?=20=EB=B1=83=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - frontend/lib/api/substitute.ts: 7개 API 함수 (Record 컨벤션) - components/admin/SubstituteSection.tsx (신규): 관리자용 대무자 지정 섹션 · 활성/예정 대무 관계 테이블, 사전 겹침 검증 · v5 토큰 (--v5-surface-solid, --v5-glow-sm) 사용, blur 금지 - components/admin/UserFormModal.tsx: 수정 모드일 때 SubstituteSection 노출 - components/layout/MySubstituteView.tsx (신규): ProfileModal 용 read-only 조회 · 내 대무자 + 내가 대무 중인 사람 양방향, D-day 카운트다운 - components/layout/ProfileModal.tsx: MySubstituteView 삽입 - app/(main)/approval/page.tsx: 대기함 행에 "대무 ← {원본 결재자}" 뱃지 · currentUser.user_id !== line.approver_id 비교 (별도 타입 필드 X) --- frontend/app/(main)/approval/page.tsx | 19 +- .../components/admin/SubstituteSection.tsx | 282 ++++++++++++++++++ frontend/components/admin/UserFormModal.tsx | 9 + .../components/layout/MySubstituteView.tsx | 115 +++++++ frontend/components/layout/ProfileModal.tsx | 4 + frontend/lib/api/substitute.ts | 115 +++++++ 6 files changed, 543 insertions(+), 1 deletion(-) create mode 100644 frontend/components/admin/SubstituteSection.tsx create mode 100644 frontend/components/layout/MySubstituteView.tsx create mode 100644 frontend/lib/api/substitute.ts diff --git a/frontend/app/(main)/approval/page.tsx b/frontend/app/(main)/approval/page.tsx index 4b57371b..6b66c7a1 100644 --- a/frontend/app/(main)/approval/page.tsx +++ b/frontend/app/(main)/approval/page.tsx @@ -16,6 +16,7 @@ import { type ApprovalRequest, type ApprovalLine, } from "@/lib/api/approval"; +import { useAuth } from "@/hooks/useAuth"; // 상태 배지 색상 const statusConfig: Record = { @@ -204,12 +205,17 @@ function ApprovalDetailModal({ request, open, onClose, onRefresh, pendingLineId // 결재 대기 행 (ApprovalLine 기반) function ApprovalLineRow({ line, onClick }: { line: ApprovalLine; onClick: () => void }) { + const { user } = useAuth(); const statusInfo = lineStatusConfig[line.status] || { label: line.status, icon: null }; const createdAt = line.request_created_at || line.created_at; const formattedDate = createdAt ? new Date(createdAt).toLocaleDateString("ko-KR", { year: "2-digit", month: "2-digit", day: "2-digit" }) : "-"; + // 대무 받은 결재: 현재 사용자가 본인이 아닌 다른 사람(원본 결재자)에게 배정된 라인을 보고 있음 + const isProxy = user?.user_id != null && line.approver_id != null + && user.user_id !== line.approver_id; + return ( + + + {error && !dialogOpen && ( +
{error}
+ )} + + {loading ? ( +
불러오는 중...
+ ) : rows.length === 0 ? ( +
지정된 대무자가 없습니다.
+ ) : ( + + + + + + + + + + + + {rows.map((r) => ( + + + + + + + + ))} + +
대무자기간사유상태
+ {r.proxy_user_name || r.proxy_user_id} + {r.proxy_dept_name && ( + ({r.proxy_dept_name}) + )} + + {r.start_date ?? "즉시"} ~ {r.end_date} + {r.reason ?? "-"}{statusBadge(r)} + +
+ )} + + + + + 대무자 지정 + + +
+
+ + setForm({ ...form, proxy_user_id: e.target.value })} + placeholder="예: hjkim" + autoFocus + /> +

+ 같은 회사 사용자만 지정 가능. SUPER_ADMIN 은 지정 불가. +

+
+ +
+
+ + setForm({ ...form, start_date: e.target.value })} + /> +

비우면 즉시 시작

+
+
+ + setForm({ ...form, end_date: e.target.value })} + /> +

그 날까지 유효

+
+
+ +
+ +