import { useState, useEffect, useCallback } from 'react'; import { bypassAccountApi, type BypassRequestResponse, type BypassAccountResponse, type PageResponse, } from '../api/bypassAccountApi'; import { useToastContext } from '../contexts/ToastContext'; import Pagination from '../components/Pagination'; import LoadingSpinner from '../components/LoadingSpinner'; const STATUS_TABS = [ { value: '', label: '전체' }, { value: 'PENDING', label: 'PENDING' }, { value: 'APPROVED', label: 'APPROVED' }, { value: 'REJECTED', label: 'REJECTED' }, ] as const; const STATUS_BADGE_COLORS: Record = { PENDING: 'bg-amber-100 text-amber-700', APPROVED: 'bg-emerald-100 text-emerald-700', REJECTED: 'bg-red-100 text-red-700', }; const PAGE_SIZE = 20; interface ApproveFormState { reviewedBy: string; accessStartDate: string; accessEndDate: string; } interface RejectFormState { reviewedBy: string; rejectReason: string; } export default function BypassAccountRequests() { const { showToast } = useToastContext(); const [pageData, setPageData] = useState | null>(null); const [loading, setLoading] = useState(true); const [statusFilter, setStatusFilter] = useState(''); const [page, setPage] = useState(0); // Approve modal const [approveTarget, setApproveTarget] = useState(null); const [approveForm, setApproveForm] = useState({ reviewedBy: '', accessStartDate: '', accessEndDate: '', }); const [approveSubmitting, setApproveSubmitting] = useState(false); // Reject modal const [rejectTarget, setRejectTarget] = useState(null); const [rejectForm, setRejectForm] = useState({ reviewedBy: '', rejectReason: '' }); const [rejectSubmitting, setRejectSubmitting] = useState(false); // Detail modal const [detailTarget, setDetailTarget] = useState(null); // Credential modal const [credentialAccount, setCredentialAccount] = useState(null); const loadRequests = useCallback(async () => { setLoading(true); try { const res = await bypassAccountApi.getRequests(statusFilter || undefined, page, PAGE_SIZE); setPageData(res.data ?? null); } catch (err) { showToast('신청 목록 조회 실패', 'error'); console.error(err); } finally { setLoading(false); } }, [showToast, statusFilter, page]); useEffect(() => { loadRequests(); }, [loadRequests]); const handleStatusFilterChange = (value: string) => { setStatusFilter(value); setPage(0); }; const openApproveModal = (req: BypassRequestResponse) => { setApproveTarget(req); setApproveForm({ reviewedBy: '', accessStartDate: '', accessEndDate: '' }); }; const openRejectModal = (req: BypassRequestResponse) => { setRejectTarget(req); setRejectForm({ reviewedBy: '', rejectReason: '' }); }; const handleApproveSubmit = async () => { if (!approveTarget) return; if (!approveForm.reviewedBy.trim()) { showToast('검토자명을 입력해주세요.', 'error'); return; } setApproveSubmitting(true); try { const res = await bypassAccountApi.approveRequest(approveTarget.id, { reviewedBy: approveForm.reviewedBy, accessStartDate: approveForm.accessStartDate || undefined, accessEndDate: approveForm.accessEndDate || undefined, }); setApproveTarget(null); setCredentialAccount(res.data); showToast('신청이 승인되었습니다.', 'success'); await loadRequests(); } catch (err) { showToast('승인 처리 실패', 'error'); console.error(err); } finally { setApproveSubmitting(false); } }; const handleRejectSubmit = async () => { if (!rejectTarget) return; if (!rejectForm.reviewedBy.trim()) { showToast('검토자명을 입력해주세요.', 'error'); return; } setRejectSubmitting(true); try { await bypassAccountApi.rejectRequest(rejectTarget.id, { reviewedBy: rejectForm.reviewedBy, rejectReason: rejectForm.rejectReason || undefined, }); setRejectTarget(null); showToast('신청이 거절되었습니다.', 'success'); await loadRequests(); } catch (err) { showToast('거절 처리 실패', 'error'); console.error(err); } finally { setRejectSubmitting(false); } }; if (loading && !pageData) return ; const requests = pageData?.content ?? []; return (
{/* 헤더 */}

계정 신청 관리

Bypass API 접근 신청을 검토하고 계정을 발급합니다.

{/* 상태 필터 탭 */}
{STATUS_TABS.map((tab) => ( ))}
{/* 테이블 */}
{requests.length === 0 ? ( ) : ( requests.map((req) => ( )) )}
상태 신청자명 신청일 요청 기간 기관 이메일 신청 사유 액션
신청 내역이 없습니다.
{req.status} {req.applicantName} {req.createdAt ? new Date(req.createdAt).toLocaleDateString('ko-KR') : '-'} {req.requestedAccessPeriod ?? '-'} {req.organization ?? '-'} {req.email ?? '-'} {req.purpose ?? '-'}
{pageData && pageData.totalPages > 1 && (
)}
{/* 승인 모달 */} {approveTarget && (
setApproveTarget(null)} >
e.stopPropagation()} >

신청 승인

{/* 신청 상세 정보 */}
신청자명: {approveTarget.applicantName}
기관: {approveTarget.organization ?? '-'}
이메일: {approveTarget.email ?? '-'}
요청 기간: {approveTarget.requestedAccessPeriod ?? '-'}
{approveTarget.purpose && (
신청 사유: {approveTarget.purpose}
)}
setApproveForm((f) => ({ ...f, reviewedBy: e.target.value }))} placeholder="검토자 이름 입력" className="w-full px-3 py-2 text-sm rounded-lg border border-wing-border bg-wing-card text-wing-text placeholder:text-wing-muted focus:outline-none focus:ring-2 focus:ring-wing-accent/50" />
setApproveForm((f) => ({ ...f, accessStartDate: e.target.value }))} className="w-full px-3 py-2 text-sm rounded-lg border border-wing-border bg-wing-card text-wing-text focus:outline-none focus:ring-2 focus:ring-wing-accent/50" />
setApproveForm((f) => ({ ...f, accessEndDate: e.target.value }))} className="w-full px-3 py-2 text-sm rounded-lg border border-wing-border bg-wing-card text-wing-text focus:outline-none focus:ring-2 focus:ring-wing-accent/50" />
)} {/* 거절 모달 */} {rejectTarget && (
setRejectTarget(null)} >
e.stopPropagation()} >

신청 거절

{/* 신청 상세 정보 */}
신청자명: {rejectTarget.applicantName}
기관: {rejectTarget.organization ?? '-'}
이메일: {rejectTarget.email ?? '-'}
요청 기간: {rejectTarget.requestedAccessPeriod ?? '-'}
{rejectTarget.purpose && (
신청 사유: {rejectTarget.purpose}
)}
setRejectForm((f) => ({ ...f, reviewedBy: e.target.value }))} placeholder="검토자 이름 입력" className="w-full px-3 py-2 text-sm rounded-lg border border-wing-border bg-wing-card text-wing-text placeholder:text-wing-muted focus:outline-none focus:ring-2 focus:ring-wing-accent/50" />