feat: allow admin delete from dashboard
This commit is contained in:
@@ -1,9 +1,11 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
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 { StatsSummary, VisitRequestView } from '../types';
|
||||||
import { STATUS_CLASS, STATUS_LABEL, formatDateTime } from '../status';
|
import { STATUS_CLASS, STATUS_LABEL, formatDateTime } from '../status';
|
||||||
import { VisitRequestDetailDialog } from '../components/VisitRequestDetailDialog';
|
import { VisitRequestDetailDialog } from '../components/VisitRequestDetailDialog';
|
||||||
|
import { Dialog } from '../components/Dialog';
|
||||||
|
import { useAuth } from '../auth/AuthContext';
|
||||||
|
|
||||||
export const DashboardPage: React.FC = () => {
|
export const DashboardPage: React.FC = () => {
|
||||||
const [items, setItems] = useState<VisitRequestView[]>([]);
|
const [items, setItems] = useState<VisitRequestView[]>([]);
|
||||||
@@ -12,12 +14,14 @@ export const DashboardPage: React.FC = () => {
|
|||||||
const [exitedIds, setExitedIds] = useState<Set<number>>(new Set());
|
const [exitedIds, setExitedIds] = useState<Set<number>>(new Set());
|
||||||
const [checkOutById, setCheckOutById] = useState<Map<number, string>>(new Map());
|
const [checkOutById, setCheckOutById] = useState<Map<number, string>>(new Map());
|
||||||
const [detailId, setDetailId] = useState<number | null>(null);
|
const [detailId, setDetailId] = useState<number | null>(null);
|
||||||
|
const [deleting, setDeleting] = useState<VisitRequestView | null>(null);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [notice, setNotice] = useState<string | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const { hasRole } = useAuth();
|
||||||
|
|
||||||
useEffect(() => {
|
const loadDashboard = () => {
|
||||||
getStatsSummary().then(setStats).catch(() => setStats(null));
|
getStatsSummary().then(setStats).catch(() => setStats(null));
|
||||||
// currently-inside visits → show "재실중" instead of the plain approved status
|
|
||||||
listInside()
|
listInside()
|
||||||
.then((rows) => setInsideIds(new Set(rows.map((r) => r.visitRequestId))))
|
.then((rows) => setInsideIds(new Set(rows.map((r) => r.visitRequestId))))
|
||||||
.catch(() => setInsideIds(new Set()));
|
.catch(() => setInsideIds(new Set()));
|
||||||
@@ -38,8 +42,32 @@ export const DashboardPage: React.FC = () => {
|
|||||||
.then(setItems)
|
.then(setItems)
|
||||||
.catch((e) => setError(e instanceof Error ? e.message : '조회 실패'))
|
.catch((e) => setError(e instanceof Error ? e.message : '조회 실패'))
|
||||||
.finally(() => setLoading(false));
|
.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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="page-head">
|
<div className="page-head">
|
||||||
@@ -48,6 +76,7 @@ export const DashboardPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && <div className="alert alert-error">{error}</div>}
|
{error && <div className="alert alert-error">{error}</div>}
|
||||||
|
{notice && <div className="alert alert-info">{notice}</div>}
|
||||||
|
|
||||||
<div className="stat-grid">
|
<div className="stat-grid">
|
||||||
<StatCard label="오늘 출입 예정" value={stats?.todayVisits ?? 0} accent="blue" />
|
<StatCard label="오늘 출입 예정" value={stats?.todayVisits ?? 0} accent="blue" />
|
||||||
@@ -67,7 +96,7 @@ export const DashboardPage: React.FC = () => {
|
|||||||
<table className="table">
|
<table className="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<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>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -98,6 +127,19 @@ export const DashboardPage: React.FC = () => {
|
|||||||
<span className={`badge badge-${STATUS_CLASS[r.status]}`}>{STATUS_LABEL[r.status]}</span>
|
<span className={`badge badge-${STATUS_CLASS[r.status]}`}>{STATUS_LABEL[r.status]}</span>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
|
<td className="row-actions">
|
||||||
|
{hasRole('ADMIN') && (
|
||||||
|
<button
|
||||||
|
className="btn-link-danger"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setDeleting(r);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
삭제
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -108,6 +150,19 @@ export const DashboardPage: React.FC = () => {
|
|||||||
{detailId != null && (
|
{detailId != null && (
|
||||||
<VisitRequestDetailDialog requestId={detailId} onClose={() => setDetailId(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>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user