import React, { useEffect, useState } from 'react'; import { listReportVisits, listTodayAccess, reportDownloadUrl } from '../api'; import { DatePickerField } from '../components/DatePickerField'; import { AccessRecord, ReportVisitView } from '../types'; import { STATUS_CLASS, STATUS_LABEL, formatDateTime } from '../status'; const pad = (n: number) => String(n).padStart(2, '0'); /** Today in YYYY-MM-DD (local). */ function todayISO(): string { const d = new Date(); return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; } /** First day of the current month in YYYY-MM-DD (local). */ function firstOfMonthISO(): string { const d = new Date(); return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-01`; } export const ReportPage: React.FC = () => { const [from, setFrom] = useState(firstOfMonthISO()); const [to, setTo] = useState(todayISO()); const [items, setItems] = useState([]); const [accessRecords, setAccessRecords] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const load = () => { setLoading(true); setError(null); Promise.all([ listReportVisits(from, to), listTodayAccess().catch(() => [] as AccessRecord[]), ]) .then(([visits, records]) => { setItems(visits); setAccessRecords(records); }) .catch((e) => { setItems([]); setAccessRecords([]); setError(e instanceof Error ? e.message : '보고서 조회에 실패했습니다.'); }) .finally(() => setLoading(false)); }; const accessById = new Map(accessRecords.map((r) => [r.visitRequestId, r])); const statusBadge = (r: ReportVisitView) => { const access = accessById.get(r.id); if (r.reportStatusLabel) { return { className: r.reportStatusLabel === '재실중' ? 'green' : r.reportStatusLabel === '퇴실' ? 'gray' : STATUS_CLASS[r.status], label: r.reportStatusLabel, }; } if (access?.inside) { return { className: 'green', label: '재실중' }; } if (access?.checkOutAt) { return { className: 'gray', label: '퇴실' }; } return { className: STATUS_CLASS[r.status], label: STATUS_LABEL[r.status] }; }; useEffect(() => { load(); // Load the default month range once; explicit 조회 handles later date changes. // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const onDownload = () => { // Trigger a download in-place via a temporary anchor. Using window.open left // a blank tab behind (the .xlsx response has no HTML to render). The anchor's // download attribute makes the browser save the file without navigating away. // Same-origin, so the session cookie is sent automatically (dev proxy / nginx). const a = document.createElement('a'); a.href = reportDownloadUrl(from, to); a.download = `visits_${from}_${to}.xlsx`; document.body.appendChild(a); a.click(); a.remove(); }; return (

출입관리 보고서

{error &&
{error}
}

조회 결과 ({items.length})

{loading ? (

불러오는 중...

) : items.length === 0 ? (

조회된 출입 신청이 없습니다.

) : ( {items.map((r) => ( ))}
방문자 회사 연락처 출입구역 호스트 출입목적 작업명 출입일시 퇴실일시 상태
{r.visitorName} {r.company || '-'} {r.contact || '-'} {r.zoneName || '-'} {r.hostName} {r.purpose || '-'} {r.workName || '-'} {formatDateTime(r.visitFrom)} {formatDateTime(r.visitTo)} {(() => { const badge = statusBadge(r); return {badge.label}; })()}
)}
); };