fix: restrict deletion to cancelled requests

This commit is contained in:
unknown
2026-07-20 19:37:30 +09:00
parent 10da3ff62d
commit 7617c9f371
3 changed files with 9 additions and 50 deletions

View File

@@ -4,7 +4,6 @@ import { deleteVisitRequest, getStatsSummary, listInside, listTodayAccess, listV
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'; import { useAuth } from '../auth/AuthContext';
export const DashboardPage: React.FC = () => { export const DashboardPage: React.FC = () => {
@@ -14,7 +13,6 @@ 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 [notice, setNotice] = useState<string | null>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@@ -48,15 +46,8 @@ export const DashboardPage: React.FC = () => {
loadDashboard(); loadDashboard();
}, []); }, []);
const confirmDelete = async (text?: string) => { const deleteCancelled = async (target: VisitRequestView) => {
const target = deleting; if (target.status !== 'CANCELLED') return;
if (!target) return;
if (text !== '삭제') {
setDeleting(null);
setError('삭제하려면 확인 입력란에 "삭제"를 입력하세요.');
return;
}
setDeleting(null);
setError(null); setError(null);
setNotice(null); setNotice(null);
try { try {
@@ -128,12 +119,12 @@ export const DashboardPage: React.FC = () => {
)} )}
</td> </td>
<td className="row-actions"> <td className="row-actions">
{hasRole('ADMIN') && ( {hasRole('ADMIN') && r.status === 'CANCELLED' && (
<button <button
className="btn-link-danger" className="btn-link-danger"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
setDeleting(r); deleteCancelled(r);
}} }}
> >
@@ -150,19 +141,6 @@ 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>
); );
}; };

View File

@@ -13,7 +13,6 @@ export const VisitRequestListPage: React.FC = () => {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const fileRef = useRef<HTMLInputElement>(null); const fileRef = useRef<HTMLInputElement>(null);
const [cancelingId, setCancelingId] = useState<number | null>(null); const [cancelingId, setCancelingId] = useState<number | null>(null);
const [deleting, setDeleting] = useState<VisitRequestView | null>(null);
const navigate = useNavigate(); const navigate = useNavigate();
const { hasRole } = useAuth(); const { hasRole } = useAuth();
@@ -39,15 +38,8 @@ export const VisitRequestListPage: React.FC = () => {
} }
}; };
const confirmDelete = async (text?: string) => { const deleteCancelled = async (target: VisitRequestView) => {
const target = deleting; if (target.status !== 'CANCELLED') return;
if (!target) return;
if (text !== '삭제') {
setDeleting(null);
setError('삭제하려면 확인 입력란에 "삭제"를 입력하세요.');
return;
}
setDeleting(null);
setError(null); setError(null);
setNotice(null); setNotice(null);
try { try {
@@ -121,8 +113,8 @@ export const VisitRequestListPage: React.FC = () => {
{(r.status === 'PENDING' || r.status === 'APPROVED') && ( {(r.status === 'PENDING' || r.status === 'APPROVED') && (
<button className="btn-link-danger" onClick={() => setCancelingId(r.id)}></button> <button className="btn-link-danger" onClick={() => setCancelingId(r.id)}></button>
)} )}
{hasRole('ADMIN') && ( {hasRole('ADMIN') && r.status === 'CANCELLED' && (
<button className="btn-link-danger" onClick={() => setDeleting(r)}></button> <button className="btn-link-danger" onClick={() => deleteCancelled(r)}></button>
)} )}
</td> </td>
</tr> </tr>
@@ -143,18 +135,6 @@ export const VisitRequestListPage: React.FC = () => {
/> />
)} )}
{deleting != null && (
<Dialog
title="출입신청 삭제"
message={`${deleting.visitorName} / ${deleting.zoneName || '-'} 신청과 관련 테스트 기록을 함께 삭제합니다. 계속하려면 "삭제"를 입력하세요.`}
withInput
inputPlaceholder="삭제"
confirmLabel="삭제"
danger
onConfirm={confirmDelete}
onCancel={() => setDeleting(null)}
/>
)}
</div> </div>
); );
}; };

View File

@@ -588,6 +588,7 @@ export function createBusinessRouter(deps: BusinessRouterDeps = { query }): Rout
const id = Number(req.params.id); const id = Number(req.params.id);
const visit = await visitById(dbQuery, id); const visit = await visitById(dbQuery, id);
if (!visit) throw new ApiError(404, '방문 신청을 찾을 수 없습니다.'); if (!visit) throw new ApiError(404, '방문 신청을 찾을 수 없습니다.');
if (visit.status !== 'CANCELLED') throw new ApiError(400, '취소 상태인 신청만 삭제할 수 있습니다.');
await dbQuery('DELETE FROM pass_deliveries WHERE visit_request_id = $1', [id]); await dbQuery('DELETE FROM pass_deliveries WHERE visit_request_id = $1', [id]);
await dbQuery('DELETE FROM approvals WHERE visit_request_id = $1', [id]); await dbQuery('DELETE FROM approvals WHERE visit_request_id = $1', [id]);