generated from pricelees/issue-pr-template
<!-- 제목 양식 --> <!-- [이슈번호] 작업 요약 (예시: [#10] Gitea 템플릿 생성) --> ## 📝 관련 이슈 및 PR **PR과 관련된 이슈 번호** - #22 ## ✨ 작업 내용 <!-- 어떤 작업을 했는지 알려주세요! --> - 기존 Thymeleaf 기반의 프론트엔드 코드를 React + Typescript 기반으로 마이그레이션 - 프론트엔드 분리에 따른 인증 API 수정 및 회원가입 API 추가 ## 🧪 테스트 <!-- 어떤 테스트를 생각했고 진행했는지 알려주세요! --> - 새로 추가된 API, 변경된 API 테스트 반영 ## 📚 참고 자료 및 기타 <!-- 참고한 자료, 또는 논의할 사항이 있다면 알려주세요! --> 프론트엔드 코드는 Gemini CLI가 구현하였고, API 관련 코드(ee21782ef9, frontend/src/api/**) 만 직접 구성 Reviewed-on: #23 Co-authored-by: pricelees <priceelees@gmail.com> Co-committed-by: pricelees <priceelees@gmail.com>
86 lines
3.2 KiB
TypeScript
86 lines
3.2 KiB
TypeScript
import { confirmWaiting, fetchWaitingReservations, rejectWaiting } from '@_api/reservation/reservationAPI';
|
|
import type { ReservationRetrieveResponse } from '@_api/reservation/reservationTypes';
|
|
import React, { useEffect, useState } from 'react';
|
|
import { useLocation, useNavigate } from 'react-router-dom';
|
|
import { isLoginRequiredError } from '@_api/apiClient';
|
|
|
|
const AdminWaitingPage: React.FC = () => {
|
|
const [waitings, setWaitings] = useState<ReservationRetrieveResponse[]>([]);
|
|
const navigate = useNavigate();
|
|
const location = useLocation();
|
|
|
|
const handleError = (err: any) => {
|
|
if (isLoginRequiredError(err)) {
|
|
alert('로그인이 필요해요.');
|
|
navigate('/login', { state: { from: location } });
|
|
} else {
|
|
const message = err.response?.data?.message || '알 수 없는 오류가 발생했습니다.';
|
|
alert(message);
|
|
console.error(err);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
const fetchData = async () => {
|
|
await fetchWaitingReservations()
|
|
.then(res => setWaitings(res.reservations))
|
|
.catch(handleError);
|
|
}
|
|
fetchData();
|
|
}, []);
|
|
|
|
const approveWaiting = async (id: number) => {
|
|
await confirmWaiting(id)
|
|
.then(() => {
|
|
alert('대기 중인 예약을 승인했어요. 결제는 별도로 진행해주세요.');
|
|
setWaitings(waitings.filter(w => w.id !== id));
|
|
})
|
|
.catch(handleError);
|
|
};
|
|
|
|
const denyWaiting = async (id: number) => {
|
|
await rejectWaiting(id)
|
|
.then(() => {
|
|
alert('대기 중인 예약을 거절했어요.');
|
|
setWaitings(waitings.filter(w => w.id !== id));
|
|
})
|
|
.catch(handleError);
|
|
};
|
|
|
|
return (
|
|
<div className="content-container">
|
|
<h2 className="content-container-title">예약 대기 관리 페이지</h2>
|
|
<div className="table-container" />
|
|
<table className="table">
|
|
<thead>
|
|
<tr>
|
|
<th scope="col">예약대기 번호</th>
|
|
<th scope="col">예약자</th>
|
|
<th scope="col">테마</th>
|
|
<th scope="col">날짜</th>
|
|
<th scope="col">시간</th>
|
|
<th scope="col"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="table-body">
|
|
{waitings.map(w => (
|
|
<tr key={w.id}>
|
|
<td>{w.id}</td>
|
|
<td>{w.member.name}</td>
|
|
<td>{w.theme.name}</td>
|
|
<td>{w.date}</td>
|
|
<td>{w.time.startAt}</td>
|
|
<td>
|
|
<button className="btn btn-primary mr-2" onClick={() => approveWaiting(w.id)}>승인</button>
|
|
<button className="btn btn-danger" onClick={() => denyWaiting(w.id)}>거절</button>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default AdminWaitingPage;
|