Initial commit: IT센터 출입자관리시스템 (ACS)
방문자 사전신청·승인, 입·출입 체크인/아웃, QR 배지, 재실현황, 블랙리스트, 대시보드 통계, 방문 리포트(엑셀)까지 7단계 전 기능 구현. - backend: Spring Boot 3.4.5 / Java 21 (JDK 26 빌드), 세션 인증, JPA, H2/PostgreSQL, POI, ZXing, Flyway - frontend: React 19 / Vite 6 / TypeScript - infra: Docker Compose (db·app·web nginx), Flyway V1__init, Python 사용자 시드 - docs: 워크플로우 / 시퀀스 다이어그램(Mermaid) / 이슈·유의사항 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
75
frontend/src/pages/DashboardPage.tsx
Normal file
75
frontend/src/pages/DashboardPage.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { getStatsSummary, listVisitRequests } from '../api';
|
||||
import { StatsSummary, VisitRequestView } from '../types';
|
||||
import { STATUS_CLASS, STATUS_LABEL, formatDateTime } from '../status';
|
||||
|
||||
export const DashboardPage: React.FC = () => {
|
||||
const [items, setItems] = useState<VisitRequestView[]>([]);
|
||||
const [stats, setStats] = useState<StatsSummary | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
getStatsSummary().then(setStats).catch(() => setStats(null));
|
||||
listVisitRequests()
|
||||
.then(setItems)
|
||||
.catch((e) => setError(e instanceof Error ? e.message : '조회 실패'))
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
const recent = items.slice(0, 8);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="page-head">
|
||||
<h2>대시보드</h2>
|
||||
<Link className="btn-primary" to="/visit-requests/new">+ 출입 신청</Link>
|
||||
</div>
|
||||
|
||||
{error && <div className="alert alert-error">{error}</div>}
|
||||
|
||||
<div className="stat-grid">
|
||||
<StatCard label="오늘 출입 예정" value={stats?.todayVisits ?? 0} accent="blue" />
|
||||
<StatCard label="현재 재실" value={stats?.currentlyInside ?? 0} accent="green" />
|
||||
<StatCard label="승인 대기" value={stats?.pending ?? 0} accent="amber" />
|
||||
<StatCard label="전체 신청" value={stats?.total ?? 0} accent="gray" />
|
||||
</div>
|
||||
|
||||
<div className="card">
|
||||
<h3>최근 출입 신청</h3>
|
||||
{loading ? (
|
||||
<p className="muted">불러오는 중…</p>
|
||||
) : recent.length === 0 ? (
|
||||
<p className="muted">신청 내역이 없습니다.</p>
|
||||
) : (
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>방문자</th><th>회사</th><th>출입구역</th><th>출입 일시</th><th>상태</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{recent.map((r) => (
|
||||
<tr key={r.id}>
|
||||
<td>{r.visitorName}</td>
|
||||
<td>{r.company || '-'}</td>
|
||||
<td>{r.zoneName || '-'}</td>
|
||||
<td>{formatDateTime(r.visitFrom)}</td>
|
||||
<td><span className={`badge badge-${STATUS_CLASS[r.status]}`}>{STATUS_LABEL[r.status]}</span></td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const StatCard: React.FC<{ label: string; value: number; accent: string }> = ({ label, value, accent }) => (
|
||||
<div className={`stat-card accent-${accent}`}>
|
||||
<div className="stat-value">{value}</div>
|
||||
<div className="stat-label">{label}</div>
|
||||
</div>
|
||||
);
|
||||
Reference in New Issue
Block a user