generated from pricelees/issue-pr-template
<!-- 제목 양식 --> <!-- [이슈번호] 작업 요약 (예시: [#10] Gitea 템플릿 생성) --> ## 📝 관련 이슈 및 PR **PR과 관련된 이슈 번호** - #46 ## ✨ 작업 내용 <!-- 어떤 작업을 했는지 알려주세요! --> - 전체 더미 데이터 추가(관리자 약 2,400건 / 회원 100만건 / 예약, 일정 약 197만건 / 결제 및 결제 상세 196만건(대략 충전식 간편결제 29.3만건, 카드 147만건, 계좌이체 19.6만건) / 테마 500건 / 매장 263건 - 로컬 애플리케이션 실행 후, 가장 병목이 되는 메인 인기 테마 쿼리만 성능 개선(5회 측정시 API 응답 시간 평균 3300 -> 90ms) ## 🧪 테스트 <!-- 어떤 테스트를 생각했고 진행했는지 알려주세요! --> 변경된 기능은 모두 테스트 반영 ## 📚 참고 자료 및 기타 <!-- 참고한 자료, 또는 논의할 사항이 있다면 알려주세요! --> 취소 데이터 등이 들어가있지 않아, 일부 컬럼에서의 Cardinality가 훨씬 낮게 나오는 상황이긴 함. 예약을 예로 들면, 현재는 확정 예약인 데이터만 추가하여 확정 예약이 100%지만, 실제 도메인의 특성상 예약 데이터는 8~90%는 확정 예약일 것으로 생각하여 큰 차이가 없다고 판단하였음. Reviewed-on: #47 Co-authored-by: pricelees <priceelees@gmail.com> Co-committed-by: pricelees <priceelees@gmail.com>
82 lines
3.2 KiB
TypeScript
82 lines
3.2 KiB
TypeScript
import '@_css/home-page-v2.css';
|
|
import React, {useEffect, useState} from 'react';
|
|
import {useNavigate} from 'react-router-dom';
|
|
import {fetchMostReservedThemes} from '@_api/theme/themeAPI';
|
|
import {DifficultyKoreanMap, mapThemeResponse, type ThemeInfoResponse} from '@_api/theme/themeTypes';
|
|
|
|
const HomePage: React.FC = () => {
|
|
const [ranking, setRanking] = useState<ThemeInfoResponse[]>([]);
|
|
const [selectedTheme, setSelectedTheme] = useState<ThemeInfoResponse | null>(null);
|
|
const navigate = useNavigate();
|
|
|
|
useEffect(() => {
|
|
const fetchData = async () => {
|
|
try {
|
|
const themeFetchCount = 10;
|
|
const response = await fetchMostReservedThemes(themeFetchCount);
|
|
setRanking(response.themes.map(mapThemeResponse));
|
|
} catch (err) {
|
|
console.error('Error fetching ranking:', err);
|
|
}
|
|
};
|
|
|
|
fetchData();
|
|
}, []);
|
|
|
|
const handleThemeClick = (theme: ThemeInfoResponse) => {
|
|
setSelectedTheme(theme);
|
|
};
|
|
|
|
const handleCloseModal = () => {
|
|
setSelectedTheme(null);
|
|
};
|
|
|
|
const handleReservationClick = (e: React.MouseEvent) => {
|
|
e.stopPropagation();
|
|
if (selectedTheme) {
|
|
navigate('/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" onClick={() => handleThemeClick(theme)}>
|
|
<img className="thumbnail" src={theme.thumbnailUrl} alt={theme.name} />
|
|
<div className="theme-info">
|
|
<h5 className="theme-name">{theme.name}</h5>
|
|
</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 modal-info-grid">
|
|
<p><strong>난이도:</strong><span>{DifficultyKoreanMap[selectedTheme.difficulty]}</span></p>
|
|
<p><strong>이용 가능 인원:</strong><span>{selectedTheme.minParticipants} ~ {selectedTheme.maxParticipants}명</span></p>
|
|
<p><strong>1인당 요금:</strong><span>{selectedTheme.price.toLocaleString()}원</span></p>
|
|
<p><strong>예상 시간:</strong><span>{selectedTheme.expectedMinutesFrom} ~ {selectedTheme.expectedMinutesTo}분</span></p>
|
|
<p><strong>이용 가능 시간:</strong><span>{selectedTheme.availableMinutes}분</span></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>
|
|
);
|
|
};
|
|
|
|
export default HomePage;
|