feat: allow admin delete from dashboard
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { getStatsSummary, listInside, listTodayAccess, listVisitRequests } from '../api';
|
||||
import { deleteVisitRequest, getStatsSummary, listInside, listTodayAccess, listVisitRequests } from '../api';
|
||||
import { StatsSummary, VisitRequestView } from '../types';
|
||||
import { STATUS_CLASS, STATUS_LABEL, formatDateTime } from '../status';
|
||||
import { VisitRequestDetailDialog } from '../components/VisitRequestDetailDialog';
|
||||
import { Dialog } from '../components/Dialog';
|
||||
import { useAuth } from '../auth/AuthContext';
|
||||
|
||||
export const DashboardPage: React.FC = () => {
|
||||
const [items, setItems] = useState<VisitRequestView[]>([]);
|
||||
@@ -12,12 +14,14 @@ export const DashboardPage: React.FC = () => {
|
||||
const [exitedIds, setExitedIds] = useState<Set<number>>(new Set());
|
||||
const [checkOutById, setCheckOutById] = useState<Map<number, string>>(new Map());
|
||||
const [detailId, setDetailId] = useState<number | null>(null);
|
||||
const [deleting, setDeleting] = useState<VisitRequestView | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [notice, setNotice] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const { hasRole } = useAuth();
|
||||
|
||||
useEffect(() => {
|
||||
const loadDashboard = () => {
|
||||
getStatsSummary().then(setStats).catch(() => setStats(null));
|
||||
// currently-inside visits → show "재실중" instead of the plain approved status
|
||||
listInside()
|
||||
.then((rows) => setInsideIds(new Set(rows.map((r) => r.visitRequestId))))
|
||||
.catch(() => setInsideIds(new Set()));
|
||||
@@ -38,8 +42,32 @@ export const DashboardPage: React.FC = () => {
|
||||
.then(setItems)
|
||||
.catch((e) => setError(e instanceof Error ? e.message : '조회 실패'))
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadDashboard();
|
||||
}, []);
|
||||
|
||||
const confirmDelete = async (text?: string) => {
|
||||
const target = deleting;
|
||||
if (!target) return;
|
||||
if (text !== '삭제') {
|
||||
setDeleting(null);
|
||||
setError('삭제하려면 확인 입력란에 "삭제"를 입력하세요.');
|
||||
return;
|
||||
}
|
||||
setDeleting(null);
|
||||
setError(null);
|
||||
setNotice(null);
|
||||
try {
|
||||
await deleteVisitRequest(target.id);
|
||||
setNotice(`${target.visitorName} 신청을 삭제했습니다.`);
|
||||
loadDashboard();
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : '삭제 실패');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="page-head">
|
||||
@@ -48,6 +76,7 @@ export const DashboardPage: React.FC = () => {
|
||||
</div>
|
||||
|
||||
{error && <div className="alert alert-error">{error}</div>}
|
||||
{notice && <div className="alert alert-info">{notice}</div>}
|
||||
|
||||
<div className="stat-grid">
|
||||
<StatCard label="오늘 출입 예정" value={stats?.todayVisits ?? 0} accent="blue" />
|
||||
@@ -67,7 +96,7 @@ export const DashboardPage: React.FC = () => {
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>방문자</th><th>회사</th><th>출입구역</th><th>출입 일시</th><th>퇴장 일시</th><th>상태</th>
|
||||
<th>방문자</th><th>회사</th><th>출입구역</th><th>출입 일시</th><th>퇴장 일시</th><th>상태</th><th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -98,6 +127,19 @@ export const DashboardPage: React.FC = () => {
|
||||
<span className={`badge badge-${STATUS_CLASS[r.status]}`}>{STATUS_LABEL[r.status]}</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="row-actions">
|
||||
{hasRole('ADMIN') && (
|
||||
<button
|
||||
className="btn-link-danger"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setDeleting(r);
|
||||
}}
|
||||
>
|
||||
삭제
|
||||
</button>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
@@ -108,6 +150,19 @@ export const DashboardPage: React.FC = () => {
|
||||
{detailId != null && (
|
||||
<VisitRequestDetailDialog requestId={detailId} onClose={() => setDetailId(null)} />
|
||||
)}
|
||||
|
||||
{deleting != null && (
|
||||
<Dialog
|
||||
title="출입신청 삭제"
|
||||
message={`${deleting.visitorName} / ${deleting.zoneName || '-'} 신청을 삭제합니다. 실제 입/퇴장 기록이 있는 신청은 삭제되지 않습니다. 계속하려면 "삭제"를 입력하세요.`}
|
||||
withInput
|
||||
inputPlaceholder="삭제"
|
||||
confirmLabel="삭제"
|
||||
danger
|
||||
onConfirm={confirmDelete}
|
||||
onCancel={() => setDeleting(null)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user