refactor: 인기 테마 조회 프론트엔드 페이지 반영

This commit is contained in:
이상진 2025-09-13 16:05:21 +09:00
parent 1d41d517b1
commit e1941052f9
4 changed files with 177 additions and 11 deletions

View File

@ -1,5 +1,11 @@
import apiClient from '../apiClient';
import type { PendingReservationCreateRequest, PendingReservationCreateResponse, ReservationDetailRetrieveResponse, ReservationSummaryRetrieveListResponse } from './reservationTypesV2';
import type {
MostReservedThemeIdListResponse,
PendingReservationCreateRequest,
PendingReservationCreateResponse,
ReservationDetailRetrieveResponse,
ReservationSummaryRetrieveListResponse
} from './reservationTypesV2';
export const createPendingReservation = async (request: PendingReservationCreateRequest): Promise<PendingReservationCreateResponse> => {
return await apiClient.post<PendingReservationCreateResponse>('/reservations/pending', request);
@ -21,3 +27,7 @@ export const fetchSummaryByMember = async (): Promise<ReservationSummaryRetrieve
export const fetchDetailById = async (reservationId: string): Promise<ReservationDetailRetrieveResponse> => {
return await apiClient.get<ReservationDetailRetrieveResponse>(`/reservations/${reservationId}/detail`);
}
export const fetchMostReservedThemeIds = async (count: number = 10): Promise<MostReservedThemeIdListResponse> => {
return await apiClient.get<MostReservedThemeIdListResponse>(`/reservations/popular-themes?count=${count}`, false);
}

View File

@ -1,5 +1,5 @@
import type { MemberSummaryRetrieveResponse } from "@_api/member/memberTypes";
import type { PaymentRetrieveResponse } from "@_api/payment/PaymentTypes";
import type {MemberSummaryRetrieveResponse} from "@_api/member/memberTypes";
import type {PaymentRetrieveResponse} from "@_api/payment/PaymentTypes";
export const ReservationStatusV2 = {
PENDING: 'PENDING',
@ -56,3 +56,7 @@ export interface ReservationDetail {
applicationDateTime: string;
payment: PaymentRetrieveResponse;
}
export interface MostReservedThemeIdListResponse {
themeIds: string[];
}

View File

@ -31,6 +31,7 @@
gap: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
cursor: pointer;
}
.theme-ranking-item-v2:hover {
@ -64,3 +65,101 @@
color: #505a67;
margin: 0;
}
/* Modal Styles */
.theme-modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.theme-modal-content {
background-color: #ffffff;
padding: 30px;
border-radius: 16px;
width: 90%;
max-width: 600px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2);
display: flex;
flex-direction: column;
gap: 20px;
}
.modal-thumbnail {
width: 100%;
height: 250px;
object-fit: cover;
border-radius: 12px;
}
.modal-theme-info h2 {
font-size: 26px;
font-weight: 700;
color: #333d4b;
margin: 0 0 10px;
}
.modal-theme-info p {
font-size: 16px;
color: #505a67;
line-height: 1.6;
margin: 0 0 15px;
}
.theme-details {
background-color: #f8f9fa;
border-radius: 8px;
padding: 15px;
}
.theme-details p {
margin: 5px 0;
font-size: 15px;
color: #333d4b;
}
.theme-details strong {
color: #191919;
}
.modal-buttons {
display: flex;
gap: 15px;
margin-top: 15px;
}
.modal-button {
flex: 1;
padding: 12px;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: background-color 0.2s;
}
.modal-button.reserve {
background-color: #007bff;
color: white;
}
.modal-button.reserve:hover {
background-color: #0056b3;
}
.modal-button.close {
background-color: #6c757d;
color: white;
}
.modal-button.close:hover {
background-color: #5a6268;
}

View File

@ -1,14 +1,31 @@
import React, { useEffect, useState } from 'react';
import { mostReservedThemes } from '../../api/theme/themeAPI';
import '../../css/home-page-v2.css';
import {fetchMostReservedThemeIds} from '@_api/reservation/reservationAPIV2';
import '@_css/home-page-v2.css';
import React, {useEffect, useState} from 'react';
import {useNavigate} from 'react-router-dom';
import {findThemesByIds} from '../../api/theme/themeAPI';
import {type UserThemeRetrieveResponse} from '../../api/theme/themeTypes';
const HomePageV2: React.FC = () => {
const [ranking, setRanking] = useState<any[]>([]);
const [ranking, setRanking] = useState<UserThemeRetrieveResponse[]>([]);
const [selectedTheme, setSelectedTheme] = useState<UserThemeRetrieveResponse | null>(null);
const navigate = useNavigate();
useEffect(() => {
const fetchData = async () => {
try {
const response = await mostReservedThemes(10);
const themeIds = await fetchMostReservedThemeIds().then(res => {
const themeIds = res.themeIds;
if (themeIds.length === 0) {
setRanking([]);
return;
}
return themeIds;
})
if (themeIds === undefined) return;
if (themeIds.length === 0) return;
const response = await findThemesByIds({ themeIds: themeIds });
setRanking(response.themes);
} catch (err) {
console.error('Error fetching ranking:', err);
@ -18,20 +35,56 @@ const HomePageV2: React.FC = () => {
fetchData();
}, []);
const handleThemeClick = (theme: UserThemeRetrieveResponse) => {
setSelectedTheme(theme);
};
const handleCloseModal = () => {
setSelectedTheme(null);
};
const handleReservationClick = (e: React.MouseEvent) => {
e.stopPropagation();
if (selectedTheme) {
navigate('/v2-1/reservation', { state: { themeId: selectedTheme.id } });
}
};
return (
<div className="home-container-v2">
<h2 className="page-title"> </h2>
<div className="theme-ranking-list-v2">
{ranking.map(theme => (
<div key={theme.id} className="theme-ranking-item-v2">
<img className="thumbnail" src={theme.thumbnail} alt={theme.name} />
<div key={theme.id} className="theme-ranking-item-v2" onClick={() => handleThemeClick(theme)}>
<img className="thumbnail" src={theme.thumbnailUrl} alt={theme.name} />
<div className="theme-info">
<h5 className="theme-name">{theme.name}</h5>
<p className="theme-description">{theme.description}</p>
</div>
</div>
))}
</div>
{selectedTheme && (
<div className="theme-modal-overlay" onClick={handleCloseModal}>
<div className="theme-modal-content" onClick={(e) => e.stopPropagation()}>
<img className="modal-thumbnail" src={selectedTheme.thumbnailUrl} alt={selectedTheme.name} />
<div className="modal-theme-info">
<h2>{selectedTheme.name}</h2>
<p>{selectedTheme.description}</p>
<div className="theme-details">
<p><strong>:</strong> {selectedTheme.difficulty}</p>
<p><strong>:</strong> {selectedTheme.price.toLocaleString()}</p>
<p><strong> :</strong> {selectedTheme.expectedMinutesFrom} ~ {selectedTheme.expectedMinutesTo}</p>
<p><strong> :</strong> {selectedTheme.minParticipants} ~ {selectedTheme.maxParticipants}</p>
</div>
</div>
<div className="modal-buttons">
<button onClick={handleReservationClick} className="modal-button reserve"></button>
<button onClick={handleCloseModal} className="modal-button close"></button>
</div>
</div>
</div>
)}
</div>
);
};