pricelees f8931ec33e [#22] 프론트엔드 React 전환 및 인증 API 수정 (#23)
<!-- 제목 양식 -->
<!-- [이슈번호] 작업 요약 (예시: [#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>
2025-07-28 10:24:20 +09:00

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;